from typing import Any, Callable, Dict, List, Literal, Optional, Union

from .structural_tag import (
    AnyTextFormat,
    ConstStringFormat,
    JSONSchemaFormat,
    QwenXMLParameterFormat,
    RegexFormat,
    SequenceFormat,
    StructuralTag,
    TagFormat,
    TagsWithSeparatorFormat,
    TriggeredTagsFormat,
)

# ---------- API Functions ----------


BuiltinSupportedModels = Literal[
    "llama",
    "qwen",
    "qwen_coder",
    "kimi",
    "deepseek_r1",
    "harmony",
    "deepseek_v3_2",
    "minimax",
    "glm47",
]


def get_builtin_structural_tag(
    model: BuiltinSupportedModels,
    reasoning: bool = True,
    tools: List[Dict[str, Any]] = [],
    builtin_tools: List[Dict[str, Any]] = [],
    force_empty_reasoning: bool = False,
) -> StructuralTag:
    r"""Get structural tag for model. This function can generate structural tag for the given model
    with the given tools, builtin tools and reasoning mode.

    Parameters
    ----------
    model : BuiltinModels
        The model type of the structural tag template.
    reasoning : bool
        Whether to enable reasoning mode. i.e. whether to enable the <think>
        and </think> tags.
    tools : List[Dict[str, Any]]
        A list of tools, each tool should have a "function" key, which is a
        dictionary containing "name" and "parameters" fields.
    builtin_tools : List[Dict[str, Any]]
        A list of builtin tools, each builtin tool should have a "function" key,
        which is a dictionary containing "name" and "parameters" fields. This
        is only used for Harmony style.
    force_empty_reasoning : bool
        Whether to force empty reasoning mode. i.e. The model will output
        the empty thinking content at the beginning of the response.
        Some models like Qwen3, DeepSeek-R1 and etc. prefer empty-thinking mode to disable
        reasoning mode instead of non-thinking mode.


    Returns
    -------
    StructuralTag
        A structural tag for function calling format.
    """
    if not isinstance(reasoning, bool):
        raise ValueError("The 'reasoning' key in the input_dict must be a boolean.")
    if not isinstance(force_empty_reasoning, bool):
        raise ValueError("The 'force_empty_reasoning' key in the input_dict must be a boolean.")
    _validate_tool_function(tools)
    _validate_tool_function(builtin_tools)

    func = _get_builtin_structural_tag_function(model)
    input_dict = {
        "tools": tools,
        "builtin_tools": builtin_tools,
        "reasoning": reasoning,
        "force_empty_reasoning": force_empty_reasoning,
    }
    return func(input_dict)


def get_builtin_structural_tag_supported_models(
    strucutural_tag_style: Optional[BuiltinSupportedModels] = None,
) -> Union[Dict[str, List[str]], List[str]]:
    """Get supported models for a given structural tag style.
    If strucutural_tag_style is not provided, return all supported models.

    Parameters
    ----------
    strucutural_tag_style : Optional[BuiltinModels]
        The structural tag style.
    Returns
    -------
    Union[Dict[str, List[str]], List[str]]
        A dictionary of supported models for each structural tag style, or a list of supported models.
    """
    if strucutural_tag_style is None:
        return _structural_tag_supported_models
    else:
        return _structural_tag_supported_models[strucutural_tag_style]


# ---------- Helper Functions And Constants ----------


_structural_tag_registry: Dict[
    BuiltinSupportedModels, Callable[[Dict[str, Any]], StructuralTag]
] = {}
_structural_tag_supported_models: Dict[BuiltinSupportedModels, List[str]] = {}
_THINK_EXCLUDE_TOKENS = ["<think>", "</think>"]


def _validate_tool_function(tools: Any) -> None:
    if not isinstance(tools, list):
        raise ValueError("The 'tools' key in the input_dict must be a list.")
    for tool in tools:
        if "function" not in tool:
            continue
        function = tool["function"]
        if "name" not in function:
            raise ValueError("Each function in the 'tools' list must have 'name' key.")
        if not isinstance(function["name"], str):
            raise ValueError("The 'name' key in each tool must be a string.")

        if ("strict" in function and function["strict"] is False) or ("parameters" not in function):
            continue
        else:
            parameters = function["parameters"]
            if not (isinstance(parameters, dict) or isinstance(parameters, bool)):
                raise ValueError("The 'parameters' key in each tool must be a dict or a boolean.")


def _get_function_parameters(function: Dict[str, Any]) -> Union[Dict[str, Any], bool]:
    if ("strict" in function and function["strict"] is False) or ("parameters" not in function):
        return True
    return function["parameters"]


def _register_builtin_structural_tag(name: str, supported_models: List[str]):
    """Register a structural tag template."""

    def decorator(func):
        _structural_tag_registry[name] = func
        _structural_tag_supported_models[name] = supported_models
        return func

    return decorator


def _get_builtin_structural_tag_function(
    format_type: BuiltinSupportedModels,
) -> Callable[[Dict[str, Any]], StructuralTag]:
    """Get builtin structural tag template function by format type.
    In all the structural tag template formats, users should provide
    a list of tools, each tool should have a "function" key, which is a dictionary
    containing "name" and "parameters" fields. Besides, for the OpenAI Harmony Response Format,
    users should also provide a list of builtin tools, each builtin tool should have a "function"
    key, which is a dictionary containing "name" and "parameters" fields. In addition, for the "qwen",
    "deepseek_r1" and "harmony" formats, "reasoning" key can be provided to enable/disable reasoning mode.
    By default, reasoning mode is enabled.

    Examples
    --------

    .. code-block:: python

        from xgrammar import get_builtin_structural_tag_template_function, Grammar
        tools = [
            {"function": {"name": "tool1", "parameters": {"param1": {"type": "string"}}}},
            {"function": {"name": "tool2", "parameters": {"param2": {"type": "integer"}}}},
        ]
        builtin_tools = [
            {"function": {"name": "builtin_tool1", "parameters": {"param1": {"type": "string"}}}},
            {"function": {"name": "builtin_tool2", "parameters": {"param2": {"type": "integer"}}}},
        ]
        template_structural_tag = get_builtin_structural_tag_template_function("harmony")
        structural_tag = template_structural_tag({"tools": tools, "builtin_tools": builtin_tools})
        grammar = Grammar.from_structural_tag(structural_tag)

    The above grammar can be used to construct a grammar that matches the function calling
    format of the specified model.



    Parameters
    ----------
    format_type : BuiltinModels
        The format type of the structural tag template.
        Currently supported format types are:
        - "llama": Llama3.1 style structural tag format.
          Supported Models: Llama 3, Llama 4 and other models that follow the same style.
        - "qwen": Qwen3 style structural tag format.
          Supported Models: Qwen3 and other models that follow the same style.
        - "qwen_coder": Qwen-Coder style structural tag format.
          Supported Models: Qwen3-Coder, Qwen3-Coder-Next and other models that follow the same style.
        - "kimi": Kimi-K2 style structural tag format.
          Supported Models: Kimi-K2, Kimi-K2.5 and other models that follow the same style.
        - "deepseek_r1": Deepseek-R1 style structural tag format.
          Supported Models: Deepseek-V3.1, Deepseek-R1, Deepseek-V3.2-exp and other models that follow the same style.
        - "harmony": OpenAI Harmony Response Format (gpt-oss).
          Supported Models: GPT-oss and other models that follow the same style.

    Returns
    -------
    Callable[[Dict[str, Any]], StructuralTag]
        The corresponding structural tag template function for the given format type.

    Raises
    ------
    ValueError
        If the format type is unknown.

    """
    func = _structural_tag_registry.get(format_type)
    if func is None:
        support_types = list(_structural_tag_registry.keys())
        raise ValueError(f"Unknown format type: {format_type}, support types: {support_types}")
    return func


# ---------- Each Built-in Structural Tag Function ----------


@_register_builtin_structural_tag("llama", ["Meta-Llama-3", "Llama-3.1", "Llama-3.2", "Llama-4"])
def _get_llama_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    """Get Llama style structural tag format.
    Reference: https://www.llama.com/docs/model-cards-and-prompt-formats/llama3_1/
    The input_dict should be a dictionary with the following keys:
    - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields.
    - "reasoning": a boolean indicating whether to enable reasoning mode.
    - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking.

    Returns
    -------
    StructuralTag
        A structural tag for function calling format.
        This format is used by Llama 3 and other models that follow the same style.

    """
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)

    tags = []
    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin=('{"name": "' + name + '", "parameters": '),
                content=JSONSchemaFormat(json_schema=parameters),
                end="}",
            )
        )

    if len(tags) > 0:
        suffix_tag = TriggeredTagsFormat(
            triggers=['{"name": '], tags=tags, excludes=_THINK_EXCLUDE_TOKENS
        )
    else:
        suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS)

    if not reasoning:
        return StructuralTag(format=suffix_tag)

    if force_empty_reasoning:
        prefix_tag = ConstStringFormat(value="<think>\n\n</think>")
    else:
        prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>")

    return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag]))


@_register_builtin_structural_tag("kimi", ["Kimi-K2", "Kimi-K2.5"])
def _get_kimi_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    """Get Kimi-K2 style structural tag format.
    Reference: https://huggingface.co/moonshotai/Kimi-K2-Instruct/blob/main/docs/tool_call_guidance.md
    The input_dict should be a dictionary with the following keys:
    - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields.
    - "reasoning": a boolean indicating whether to enable reasoning mode.
    - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking.

    Returns
    -------
    StructuralTag
        A structural tag template.
        This format is used by Kimi-K2 and other models that follow the same style.
    """
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)

    tags = []
    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin=f"<|tool_call_begin|>functions.{name}:",
                content=SequenceFormat(
                    elements=[
                        RegexFormat(pattern=r"\d+"),
                        ConstStringFormat(value="<|tool_call_argument_begin|>"),
                        JSONSchemaFormat(json_schema=parameters),
                    ]
                ),
                end="<|tool_call_end|>",
            )
        )

    if len(tags) > 0:
        suffix_tag = TriggeredTagsFormat(
            triggers=["<|tool_call_begin|>"], tags=tags, excludes=_THINK_EXCLUDE_TOKENS
        )
    else:
        suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS)

    if not reasoning:
        return StructuralTag(format=suffix_tag)

    if force_empty_reasoning:
        prefix_tag = ConstStringFormat(value="<think></think>")
    else:
        prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>")

    return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag]))


@_register_builtin_structural_tag(
    "deepseek_r1", ["DeepSeek-V3.1", "DeepSeek-R1", "DeepSeek-V3.2-exp"]
)
def _get_deepseek_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    """Get DeepSeek-R1 style structural tag format.
    Reference: https://huggingface.co/deepseek-ai/DeepSeek-V3.1/blob/main/tokenizer_config.json
    The input_dict should be a dictionary with the following keys:
    - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields.
    - "reasoning": a boolean indicating whether to enable reasoning mode.
    - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking.

    Returns
    -------
    StructuralTag
        A structural tag for function calling format.
        This format is used by DeepSeek-R1 and other models that follow the same style.

    """
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)

    tags = []
    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin=f"<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>{name}<｜tool▁sep｜>",
                content=JSONSchemaFormat(json_schema=parameters),
                end="<｜tool▁call▁end｜>",
            )
        )

    if len(tags) > 0:
        suffix_tag = TriggeredTagsFormat(
            triggers=["<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>"],
            tags=tags,
            excludes=_THINK_EXCLUDE_TOKENS,
        )
    else:
        suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS)

    if not reasoning:
        return StructuralTag(format=suffix_tag)

    if force_empty_reasoning:
        prefix_tag = ConstStringFormat(value="</think>")
    else:
        prefix_tag = TagFormat(begin="", content=AnyTextFormat(), end="</think>")

    return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag]))


@_register_builtin_structural_tag("qwen_coder", ["Qwen3-Coder", "Qwen3-Coder-Next"])
def _get_qwen_coder_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    """Get Qwen3-Coder style structural tag format.
    Reference: https://huggingface.co/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8/blob/main/chat_template.jinja
    The input_dict should be a dictionary with the following keys:
    - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields.
    - "reasoning": a boolean indicating whether to enable reasoning mode.
    - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking.

    Returns
    -------
    StructuralTag
        A structural tag for function calling format.
        This format is used by Qwen3-Coder and other models that follow the same style.
    """
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)

    tags = []
    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin=f"<tool_call>\n<function={name}>\n",
                content=QwenXMLParameterFormat(json_schema=parameters),
                end="\n</function>\n</tool_call>",
            )
        )

    if len(tags) > 0:
        suffix_tag = TriggeredTagsFormat(
            triggers=["<tool_call>\n<function="], tags=tags, excludes=_THINK_EXCLUDE_TOKENS
        )
    else:
        suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS)

    if not reasoning:
        return StructuralTag(format=suffix_tag)

    if force_empty_reasoning:
        prefix_tag = ConstStringFormat(value="<think>\n\n</think>")
    else:
        prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>")

    return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag]))


@_register_builtin_structural_tag("qwen", ["Qwen3"])
def _get_qwen_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    """Get Qwen3 style structural tag format.
    Reference: https://qwen.readthedocs.io/en/latest/framework/function_call.html
    The input_dict should be a dictionary with the following keys:
    - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields.
    - "reasoning": a boolean indicating whether to enable reasoning mode.

    Returns
    -------
    StructuralTag
        A structural tag template.
        This format is used by Qwen3 and other models that follow the same style.

    """
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)

    tags = []
    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin=('<tool_call>\n{"name": "' + name + '", "arguments": '),
                content=JSONSchemaFormat(json_schema=parameters),
                end="}\n</tool_call>",
            )
        )
    if len(tags) > 0:
        suffix_tag = TriggeredTagsFormat(
            triggers=["<tool_call>"], tags=tags, excludes=_THINK_EXCLUDE_TOKENS
        )
    else:
        suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS)

    if not reasoning:
        return StructuralTag(format=suffix_tag)

    if force_empty_reasoning:
        prefix_tag = ConstStringFormat(value="<think>\n\n</think>")
    else:
        prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>")

    sequence_format = SequenceFormat(elements=[prefix_tag, suffix_tag])
    return StructuralTag(format=sequence_format)


@_register_builtin_structural_tag("harmony", ["gpt-oss"])
def _get_harmony_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    """Get harmony(gpt-oss) style structural tag format.
    Reference: https://developers.openai.com/cookbook/articles/openai-harmony
    Reference: https://huggingface.co/openai/gpt-oss-120b/blob/main/chat_template.jinja
    The input_dict should be a dictionary with the following keys:
    - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields.
    - "builtin_tools": a list of builtin tools, each builtin tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields.
    - "reasoning": a boolean indicating whether to enable reasoning mode.
    - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking.

    Returns
    -------
    StructuralTag
        A structural tag template.
        This format is in OpenAI Harmony Response Format, which is used by GPT-oss
        and other models that follow the same style.

    """
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)
    builtin_tools = input_dict.get("builtin_tools", [])

    tags = []

    if reasoning:
        if force_empty_reasoning:
            analysis_tag = TagFormat(
                begin="<|channel|>analysis<|message|>",
                content=ConstStringFormat(value="<|end|>"),
                end="",
            )
        else:
            analysis_tag = TagFormat(
                begin="<|channel|>analysis<|message|>", content=AnyTextFormat(), end="<|end|>"
            )
        tags.append(analysis_tag)

    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin=f"<|channel|>commentary to={name}<|constrain|>json<|message|>",
                content=JSONSchemaFormat(json_schema=parameters),
                end="<|call|>",
            )
        )

    for tool in builtin_tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin=f"<|channel|>analysis to={name}<|message|>",
                content=JSONSchemaFormat(json_schema=parameters),
                end="<|call|>",
            )
        )

    final_tag = TagFormat(
        begin="<|channel|>final<|message|>", content=AnyTextFormat(), end="<|end|>"
    )

    tags.append(final_tag)
    tags_with_separator = TagsWithSeparatorFormat(tags=tags, separator="<|start|>assistant")
    return StructuralTag(format=tags_with_separator)


@_register_builtin_structural_tag("deepseek_v3_2", ["DeepSeek-V3.2"])
def _get_deepseek_v3_2_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)

    tags = []
    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin='<｜DSML｜invoke name="' + name + '">\n',
                content=JSONSchemaFormat(json_schema=parameters, style="deepseek_xml"),
                end="</｜DSML｜invoke>\n",
            )
        )

    # generate function calling triggered tag
    if len(tags) > 0:
        function_calling_tags = TagsWithSeparatorFormat(
            tags=tags, separator="\n", at_least_one=True
        )

        suffix_tag = TriggeredTagsFormat(
            triggers=["<｜DSML｜function_calls>"],
            tags=[
                TagFormat(
                    begin="<｜DSML｜function_calls>\n",
                    content=function_calling_tags,
                    end="</｜DSML｜function_calls>\n",
                )
            ],
            excludes=_THINK_EXCLUDE_TOKENS,
        )
    else:
        suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS)

    if not reasoning:
        return StructuralTag(format=suffix_tag)

    if force_empty_reasoning:
        prefix_tag = ConstStringFormat(value="<think>\n\n</think>")
    else:
        prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>")

    sequence_format = SequenceFormat(elements=[prefix_tag, suffix_tag])
    return StructuralTag(format=sequence_format)


@_register_builtin_structural_tag("minimax", ["MiniMax-M2.5"])
def _get_minimax_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)

    tags = []
    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = _get_function_parameters(function)
        name = function["name"]
        tags.append(
            TagFormat(
                begin='<invoke name="' + name + '">\n',
                content=JSONSchemaFormat(json_schema=parameters, style="minimax_xml"),
                end="</invoke>\n",
            )
        )

    # generate function calling triggered tag
    if len(tags) > 0:
        function_calling_tags = TagsWithSeparatorFormat(
            tags=tags, separator="\n", at_least_one=True
        )

        suffix_tag = TriggeredTagsFormat(
            triggers=["<minimax:tool_call>"],
            tags=[
                TagFormat(
                    begin="<minimax:tool_call>\n",
                    content=function_calling_tags,
                    end="</minimax:tool_call>\n",
                )
            ],
            excludes=_THINK_EXCLUDE_TOKENS,
        )
    else:
        suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS)

    if not reasoning:
        return StructuralTag(format=suffix_tag)

    if force_empty_reasoning:
        prefix_tag = ConstStringFormat(value="<think>\n\n</think>")
    else:
        prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>")

    sequence_format = SequenceFormat(elements=[prefix_tag, suffix_tag])
    return StructuralTag(format=sequence_format)


@_register_builtin_structural_tag("glm47", ["GLM-5", "GLM-4.7"])
def _get_glm47_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag:
    """Get GLM-4.7/GLM-5 style structural tag format.

    The GLM tool calling format uses XML-like tags:
    <tool_call>function_name
    <arg_key>key</arg_key><arg_value>value</arg_value>
    </tool_call>

    The input_dict should be a dictionary with the following keys:
    - "tools": a list of tools, each tool should have a "function" key, which is a dictionary
      containing "name" and "parameters" fields.
    - "reasoning": a boolean indicating whether to enable reasoning mode.
    - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking,
      if False use thinking.

    Returns
    -------
    StructuralTag
        A structural tag for GLM function calling format.
    """
    tools = input_dict.get("tools", [])
    reasoning = input_dict.get("reasoning", True)
    force_empty_reasoning = input_dict.get("force_empty_reasoning", False)

    tags = []
    for tool in tools:
        if "function" not in tool:
            continue

        function = tool["function"]
        parameters = function["parameters"]
        name = function["name"]
        tags.append(
            TagFormat(
                begin=f"<tool_call>{name}",
                content=JSONSchemaFormat(json_schema=parameters, style="glm_xml"),
                end="</tool_call>",
            )
        )

    if len(tags) > 0:
        suffix_tag = TriggeredTagsFormat(
            triggers=["<tool_call>"], tags=tags, excludes=_THINK_EXCLUDE_TOKENS
        )
    else:
        suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS)

    if not reasoning:
        return StructuralTag(format=suffix_tag)

    if force_empty_reasoning:
        prefix_tag = ConstStringFormat(value="<think>\n\n</think>")
    else:
        prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>")

    return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag]))
