
from dataclasses import dataclass
from typing import Union, List, Dict, Any
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup,WebAppInfo


def chunks(input_data, sizes: int | list | tuple, reverse=False, append_last=True):
    """
    Splits a list or dictionary into sublists/subdicts based on the specified sizes.

    :param input_data: The list or dictionary to be split
    :param sizes: A tuple or list of integers representing the sizes of the sublists/subdicts
    :param reverse: If True, reverses each chunk
    :param append_last: If True, appends remaining elements as last chunk
    :return: A list of sublists/subdicts split according to the sizes
    """
    do_reverse = lambda x: x[::-1] if reverse else x

    # Handle dictionary input
    if isinstance(input_data, dict):
        items = list(input_data.items())
        chunks_result = chunks(items, sizes, reverse, append_last)
        return [dict(chunk) for chunk in chunks_result]

    # Original list handling
    if isinstance(sizes, int):
        res = []
        for i in range(0, len(input_data), sizes):
            res += [do_reverse(input_data[i:i + sizes])]
        return res

    result = []
    start_index = 0

    for size in sizes:
        end_index = start_index + size
        result.append(do_reverse(input_data[start_index:end_index]))
        start_index = end_index
    if append_last and start_index < len(input_data):
        result.append(do_reverse(input_data[start_index:]))

    return result


import json
@dataclass
class ButtonBuilder:
    data: Union[List[str], Dict]
    sizes: Union[int, List, tuple]
    reverse: bool = False
    close: bool = True


    def build(self, **kwargs) -> Union[ReplyKeyboardMarkup, InlineKeyboardMarkup]:
        """
            Builds and returns a Telegram keyboard based on input data type.

            Args:
                **kwargs: Configuration options for keyboard creation
                    - resize: Allow keyboard resizing (default: True)
                    - share_phone: Add phone sharing button (default: False)
                    - KeyboardButtonKwargs: Additional args for KeyboardButton
                    - ReplyKeyboardMarkupKwargs: Additional args for ReplyKeyboardMarkup
                    - close_callback_data: Custom callback data for close button (default: 'close')
                    - close_title: Custom title for close button (default: 'close')

            Returns:
                ReplyKeyboardMarkup: If input data is a list
                InlineKeyboardMarkup: If input data is a dictionary

            Examples:
                # Create reply keyboard
                kb = KeyboardBuilder(['btn1', 'btn2'], sizes=2).build(resize=True)

                # Create inline keyboard
                kb = KeyboardBuilder({'Button': 'callback_data'}, sizes=1).build(
                    close_title='Close Menu',
                    close_callback_data='menu_close'
                )
            """
        if isinstance(self.data, list):
            return self._build_reply_keyboard(**kwargs)
        return self._build_inline_keyboard(**kwargs)

    def _build_reply_keyboard(self, **kwargs) -> ReplyKeyboardMarkup:
        resize = kwargs.get('resize', True)
        share_phone = kwargs.get('share_phone', False)
        button_kwargs = kwargs.get('KeyboardButtonKwargs', {})
        markup_kwargs = kwargs.get('ReplyKeyboardMarkupKwargs', {})

        data = self._chunk_data(self.data.copy())
        buttons = [
            [KeyboardButton(col, request_contact=share_phone, **button_kwargs)
             for col in row]
            for row in data
        ]
        return ReplyKeyboardMarkup(buttons, resize, **markup_kwargs)

    def _build_inline_keyboard(self, **kwargs) -> InlineKeyboardMarkup:
        close_data = kwargs.get('close_callback_data',json.dumps({'act':'close'}) )
        close_title = kwargs.get('close_title', 'close')
        buttons = []
        for key, value in self.data.items():
            if isinstance(value, str):
                if any(prefix in value for prefix in ('http://', 'https://')):
                    buttons.append(InlineKeyboardButton(key, url=value))
                else:
                    raise ValueError(f"Invalid value type in data\n{value}")
                    # buttons.append(InlineKeyboardButton(key, callback_data=value))
            elif isinstance(value, dict):
                json_data = json.dumps(value)
                if len(json_data)>64:
                    raise ValueError(f"data len more 64 bytes\n{value}")
                buttons.append(InlineKeyboardButton(key,callback_data=json_data))
            elif isinstance(value, WebAppInfo):
                buttons+=[InlineKeyboardButton(key,web_app=value)]
            else:
                raise ValueError(f"Invalid value type in data\n{value}")
        if self.close:
            buttons.append(InlineKeyboardButton(close_title, callback_data=close_data))
        return InlineKeyboardMarkup(self._chunk_data(buttons))

    def _chunk_data(self, data: List) -> List:
        if isinstance(self.sizes, int) and self.sizes == 0:
            return [data]

        return chunks(data, self.sizes, reverse=self.reverse)

