# Copyright (c) Microsoft. All rights reserved.

import json
import logging
import re
from collections.abc import Callable
from enum import Enum
from xml.etree.ElementTree import Element, SubElement, tostring  # nosec B405

logger: logging.Logger = logging.getLogger(__name__)


def _messages(this, options, *args, **kwargs):
    from semantic_kernel.contents.chat_history import ChatHistory

    if not isinstance(this.context["chat_history"], ChatHistory):
        return ""
    return this.context["chat_history"].to_prompt()


def _message_to_prompt(this, *args, **kwargs):
    from semantic_kernel.contents.chat_message_content import ChatMessageContent

    if isinstance(this.context, ChatMessageContent):
        return str(this.context.to_prompt())
    return str(this.context)


def _message(this, options, *args, **kwargs):
    from semantic_kernel.contents.chat_message_content import ChatMessageContent
    from semantic_kernel.contents.const import CHAT_MESSAGE_CONTENT_TAG

    # When the context is a ChatMessageContent, delegate to to_element() so that
    # the XML contract is consistent with the Jinja2 path.
    if isinstance(this.context, ChatMessageContent):
        message = this.context.to_element()
        return tostring(message, encoding="unicode", short_empty_elements=False)

    # Fallback: build the element manually from kwargs and block content.
    from semantic_kernel.contents.const import TEXT_CONTENT_TAG

    message = Element(CHAT_MESSAGE_CONTENT_TAG)
    for key, value in kwargs.items():
        if isinstance(value, Enum):
            value = value.value
        if value is not None:
            message.set(key, str(value))
    try:
        content = str(options["fn"](this))
    except Exception:  # pragma: no cover
        content = ""
    if content:
        text_elem = SubElement(message, TEXT_CONTENT_TAG)
        text_elem.text = content
    return tostring(message, encoding="unicode", short_empty_elements=False)


def _set(this, *args, **kwargs):
    if "name" in kwargs and "value" in kwargs:
        this.context[kwargs["name"]] = kwargs["value"]
    if len(args) == 2 and isinstance(args[0], str):
        this.context[args[0]] = args[1]
    return ""


def _get(this, *args, **kwargs):
    if len(args) == 0:
        return ""
    return this.context.get(args[0], "")


def _array(this, *args, **kwargs):
    return list(args)


def _range(this, *args, **kwargs):
    args = list(args)
    for index, arg in enumerate(args):
        if not isinstance(arg, int):
            try:
                args[index] = int(arg)
            except ValueError:
                args.pop(index)
    if len(args) == 1:
        return list(range(args[0]))
    if len(args) == 2:
        return list(range(args[0], args[1]))
    if len(args) == 3:
        return list(range(args[0], args[1], args[2]))
    return []


def _concat(this, *args, **kwargs):
    return "".join([str(value) for value in args])


def _or(this, *args, **kwargs):
    return any(args)


def _add(this, *args, **kwargs):
    return sum([float(value) for value in args])


def _subtract(this, *args, **kwargs):
    return float(args[0]) - sum([float(value) for value in args[1:]])


def _equals(this, *args, **kwargs):
    return args[0] == args[1]


def _less_than(this, *args, **kwargs):
    return float(args[0]) < float(args[1])


def _greater_than(this, *args, **kwargs):
    return float(args[0]) > float(args[1])


def _less_than_or_equal(this, *args, **kwargs):
    return float(args[0]) <= float(args[1])


def _greater_than_or_equal(this, *args, **kwargs):
    return float(args[0]) >= float(args[1])


def _json(this, *args, **kwargs):
    if not args:
        return ""
    return json.dumps(args[0])


def _double_open(this, *args, **kwargs):
    return "{{"


def _double_close(this, *args, **kwargs):
    return "}}"


def _camel_case(this, *args, **kwargs):
    return "".join([word.capitalize() for word in args[0].split("_")])


def _snake_case(this, *args, **kwargs):
    arg = args[0]
    arg = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", arg)
    arg = re.sub(r"([a-z\d])([A-Z])", r"\1_\2", arg)
    arg = arg.replace("-", "_")
    return arg.lower()


HANDLEBAR_SYSTEM_HELPERS: dict[str, Callable] = {
    "set": _set,
    "get": _get,
    "array": _array,
    "range": _range,
    "concat": _concat,
    "or": _or,
    "add": _add,
    "subtract": _subtract,
    "equals": _equals,
    "less_than": _less_than,
    "lessThan": _less_than,
    "greater_than": _greater_than,
    "greaterThan": _greater_than,
    "less_than_or_equal": _less_than_or_equal,
    "lessThanOrEqual": _less_than_or_equal,
    "greater_than_or_equal": _greater_than_or_equal,
    "greaterThanOrEqual": _greater_than_or_equal,
    "json": _json,
    "double_open": _double_open,
    "doubleOpen": _double_open,
    "double_close": _double_close,
    "doubleClose": _double_close,
    "camel_case": _camel_case,
    "camelCase": _camel_case,
    "snake_case": _snake_case,
    "snakeCase": _snake_case,
    "message": _message,
    "message_to_prompt": _message_to_prompt,
    "messageToPrompt": _message_to_prompt,
    "messages": _messages,
}
