# TODO: This must not depend on 'youtube_autonomous'
from youtube_autonomous.segments.enums import ShortcodeStart, ShortcodeDuration
from youtube_autonomous.experimental.shortcodes.consts import FILE_DURATION
from yta_audio.voice.transcription.objects import AudioTranscription
from yta_general_utils.programming.parameter_validator import PythonValidator
from typing import Union

from yta_shortcodes.tag_type import ShortcodeTagType


LOWER_INDEX = -1
UPPER_INDEX = 99999

class Shortcode:
    """
    Base shortcode class that represent the minimum
    a shortcode can have. This class is pretended 
    to be used as a model to create a custom class.

    This class represent a shortcode once its been
    detected in the code with the Shortcode parser
    based on its corresponding shortcode tag that
    was registered to enable the detection, and
    includes the attributes found, the content (if
    existing) and the indexes of the words that 
    were inmediately before the start and end tag.
    """

    name: str
    """
    The shortcode name that represent the shortcode
    meaning.
    """
    _type: ShortcodeTagType
    """
    The shortcode type, that can be 'simple' or
    'block' scoped.
    """
    _context: any
    """
    TODO: I don't know what this is for
    """
    content: Union[None, str]
    """
    The content of the shortcode, that will be None
    if a simple-scoped shortcode, or a string if
    block-scoped.
    """
    attributes: list[dict]
    """
    The list of attributes found when reading the
    shortcode. These attributes can be single values
    (from args) or key-values (from kwargs).
    """
    start_previous_word_index: Union[int, None]
    """
    The index of the word that is inmediately before
    the start tag of this shortcode. Could be None if
    the shortcode is just at the begining. This index
    is considered within the text empty of shortcodes.
    """
    end_previous_word_index: Union[int, None]
    """
    The index of the word that is inmediately before
    the end tag of this shortcode. It is None if the
    shortcode is a simple one. This index is 
    considered within the text empty of shortcodes.
    """

    def __init__(
        self,
        tag: str,
        type: ShortcodeTagType,
        context: any,
        content: str,
        attributes: list[dict],
        start_previous_word_index: Union[int, None] = None,
        end_previous_word_index: Union[int, None] = None,
    ):
        type = ShortcodeTagType.to_enum(type)

        self.tag = tag
        self.type = type
        self._context = context
        self.content = content
        self.attributes = attributes
        self.start_previous_word_index = start_previous_word_index
        self.end_previous_word_index = end_previous_word_index

    @property
    def is_block_scoped(self):
        """
        Check if this shortcode is a block-scoped one
        [shortcode_tag] ... [/shortcode_tag].
        """
        return self._type == ShortcodeTagType.BLOCK
    
    @property
    def is_simple_scoped(self):
        """
        Check if this shortcode is a simple-scoped one
        [shortcode_tag].
        """
        return self._type == ShortcodeTagType.SIMPLE

class YTAShortcode(Shortcode):
    """
    Custom shortcode class that includes the
    ability to calculate the 'start' and 
    'duration' fields from a given transcription
    of the text in which it has been found. It
    will use the words next to the shortcode and
    their transcription time moment to obtain
    this shortcode 'start' and 'duration' values.
    """

    start: Union[ShortcodeStart, float, None] = None
    """
    The start time moment in which the shortcode 
    behaviour must be applied. This needs to be 
    calculated with a transcription of the text
    provided when obtaining this shortcode.
    """
    duration: Union[ShortcodeDuration, float, None] = None
    """
    The time the shortcode behaviour must last. This
    needs to be calculated with a transcription of
    the text provided when obtaining this shortcode.
    """

    @property
    def end(self) -> Union[float, None]:
        """
        The end time moment in which the shortcode
        behaviour must end. This is calculated with the
        'start' and 'duration' parameters if set and
        valid and returned as a float number, or as None
        if not able to calculate.
        """
        if (
            PythonValidator.is_number(self.start) and
            PythonValidator.is_number(self.duration)
        ):
            return self.start + self.duration
        
        return None

    def __init__(
        self,
        tag: str,
        type: ShortcodeTagType,
        context: any,
        content: str,
        attributes: list[dict],
        start_previous_word_index: Union[int, None] = None,
        end_previous_word_index: Union[int, None] = None,
    ):
        super().__init__(tag, type, context, content, attributes, start_previous_word_index, end_previous_word_index)
        self.start = float(attributes.get('start', None)) if attributes.get('start', None) is not None else None
        self.duration = float(attributes.get('duration', None)) if attributes.get('duration', None) is not None else None

    def calculate_start_and_duration(
        self,
        transcription: AudioTranscription
    ):
        """
        Processes this shortcode 'start' and 'duration'
        fields by using the 'transcription' if needed
        (if 'start' and 'duration' fields are not numbers
        manually set by the user in the shortcode when
        written).

        This will consider the current 'start' and
        'duration' strategy and apply them to the words
        related to the shortcode to obtain the real 'start'
        and 'duration' number values.
        """
        # TODO: Here below I have simplified the code but
        # it is commented because if the value is not one
        # in the dict it will fail... but I think there is
        # no possibility of being not one in the dict
        if PythonValidator.is_instance(self.start, ShortcodeStart):
            if self.is_simple_scoped:
                # self.start = {
                #     ShortcodeStart.BETWEEN_WORDS: (transcription.words[self.start_previous_word_index].start + transcription.words[self.start_previous_word_index + 1].start) / 2
                # }[self.start]

                if self.start == ShortcodeStart.BETWEEN_WORDS:
                    self.start = (transcription.words[self.start_previous_word_index].start + transcription.words[self.start_previous_word_index + 1].start) / 2
            else:
                # self.start = {
                #     ShortcodeStart.START_OF_FIRST_SHORTCODE_CONTENT_WORD: transcription.words[self.start_previous_word_index + 1].start,
                #     ShortcodeStart.MIDDLE_OF_FIRST_SHORTCODE_CONTENT_WORD: (transcription.words[self.start_previous_word_index + 1].start + transcription.words[self.start_previous_word_index + 1].end) / 2,
                #     ShortcodeStart.END_OF_FIRST_SHORTCODE_CONTENT_WORD: transcription.words[self.start_previous_word_index + 1].end
                # }[self.start]

                if self.start == ShortcodeStart.START_OF_FIRST_SHORTCODE_CONTENT_WORD:
                    self.start = transcription.words[self.start_previous_word_index + 1].start
                elif self.start == ShortcodeStart.MIDDLE_OF_FIRST_SHORTCODE_CONTENT_WORD:
                    self.start = (transcription.words[self.start_previous_word_index + 1].start + transcription.words[self.start_previous_word_index + 1].end) / 2
                elif self.start == ShortcodeStart.END_OF_FIRST_SHORTCODE_CONTENT_WORD:
                    self.start = transcription.words[self.start_previous_word_index + 1].end

        if PythonValidator.is_instance(self.duration, ShortcodeDuration):
            if self.type == ShortcodeTagType.SIMPLE:
                # self.duration = {
                #     ShortcodeDuration.FILE_DURATION: FILE_DURATION
                # }[self.duration]

                if self.duration == ShortcodeDuration.FILE_DURATION:
                    # This duration must be set when the file is ready, so 
                    # we use a number value out of limits to flag it
                    self.duration = FILE_DURATION
            else:
                # self.duration = {
                #     ShortcodeDuration.SHORTCODE_CONTENT: transcription.words[self.previous_end_word_index].end - transcription.words[self.previous_start_word_index + 1].start
                # }[self.duration]

                if self.duration == ShortcodeDuration.SHORTCODE_CONTENT:
                    self.duration = transcription.words[self.end_previous_word_index].end - transcription.words[self.end_previous_word_index + 1].start


# TODO: Delete this when refactored
# class Shortcode:
#     """
#     Class that represent a shortcode detected in a text, containing 
#     its attributes and values and also the content if block-scoped.
#     """

#     name: str
#     """
#     The tag name, which is the word that comes
#     inmediately after the opening square bracket
#     and represent the shortcode.
#     """
#     _type: ShortcodeTagType
#     _start: float
#     _duration: float
#     _keywords: str
#     _filename: str
#     _url: str
#     _mode: ShortcodeMode
#     _context: any # TODO: Do I really need this (?)
#     _content: str
#     _previous_start_word_index: int
#     _previous_end_word_index: int

#     def __init__(self, name: str, type: ShortcodeTagType, attributes: dict, context, content: str):
#         """
#         The shortcode has a 'type' and could include some 'attributes' that
#         are the parameters inside the brackets, that could also be simple or
#         include an associated value. If it is a block-scoped shortcode, it
#         will have some 'content' inside of it.
#         """
#         self.name = name
#         self.type = type

#         # TODO: Refactor this
#         self.config = Configuration.get_configuration_by_type(self.name).config_as_shortcode
        
#         self.start = attributes.get(ShortcodeField.START.value, None)
#         self.duration = attributes.get(ShortcodeField.DURATION.value, None)
#         self.keywords = attributes.get(ShortcodeField.KEYWORDS.value, '')
#         self.filename = attributes.get(ShortcodeField.FILENAME.value, '')
#         self.url = attributes.get(ShortcodeField.URL.value, '')
#         self.mode = attributes.get(ShortcodeField.MODE.value, None)

#         # TODO: Should I treat these shortcodes as Enhancement so I need
#         # to make the same checks (more specific that these ones below) (?)

#         if not self.keywords and not self.filename and not self.url:
#             raise Exception('No "keywords" nor "filename" nor "url" sources available.')

#         # TODO: Do we actually need the context (?)
#         self.context = context
#         # TODO: Do we actually need the content (?)
#         self.content = content

#         # TODO: We are not acepting None
#         self.previous_start_word_index = None
#         self.previous_end_word_index = None

#         # TODO: Maybe we need to create an abstract Shortcode class to inherit
#         # in specific shortcode classes

#     @property
#     def tag(self):
#         """
#         The shortcode tag that represents and identifies it. It
#         determines the way it will be built and applied.
#         """
#         return self._tag
    
#     @tag.setter
#     def tag(self, tag: Union[ShortcodeTagType, str]):
#         self._tag = ShortcodeTagType.to_enum(tag)

#     @property
#     def type(self):
#         """
#         The type of the shortcode, that identifies its structure and
#         parameters it must have.
#         """
#         return self._type
    
#     @type.setter
#     def type(self, type: Union[ShortcodeTagType, str]):
#         self._type = ShortcodeTagType.to_enum(type)
        
#     @property
#     def start(self):
#         """
#         The time moment of the current segment in which this element is
#         expected to be applied.
#         """
#         return self.start
    
#     @start.setter
#     def start(self, start: Union[ShortcodeStart, int, float, None]):
#         if start is None:
#             if self.type == ShortcodeTagType.BLOCK:
#                 start = ShortcodeStart.START_OF_FIRST_SHORTCODE_CONTENT_WORD
#             else:
#                 start = ShortcodeStart.BETWEEN_WORDS

#         self._start = ElementValidator.validate_shortcode_start_field(start)

#     @property
#     def duration(self):
#         """
#         The duration of the shortcode, that it is calculated according to the
#         field or to its content.
#         """
#         return self._duration
    
#     @duration.setter
#     def duration(self, duration: Union[ShortcodeDuration, int, float, None]):
#         if duration is None:
#             if self.type == ShortcodeTagType.BLOCK:
#                 duration = ShortcodeDuration.SHORTCODE_CONTENT
#             else:
#                 duration = ShortcodeDuration.FILE_DURATION

#         # TODO: I'm setting "file_duration" but checking if it is 99998 value
#         # so it is failing. Should I put the numeric value here (?)
#         self._duration = ElementValidator.validate_shortcode_duration_field(duration.name)

#     @property
#     def mode(self):
#         return self._mode
    
#     @mode.setter
#     def mode(self, mode: Union[ShortcodeMode, str]):
#         mode = ShortcodeMode.to_enum(mode)

#         if mode not in self.config.modes:
#             raise Exception(f'The provided "{mode.value}" is not a valid mode for this shortcode type "{self.type.value}" and tag "{self.tag.value}".')

#         self._mode = mode
    
#     @property
#     def context(self):
#         """
#         The context of the shortcode.

#         TODO: Do I really need this (?)
#         """
#         return self._context
    
#     @context.setter
#     def context(self, context: any):
#         # TODO: Do I need to check something (?)
#         self._context = context

#     @property
#     def content(self):
#         """
#         The text that is between the shortcode open and end tag
#         and can include shortcodes. This parameter makes sense if
#         the shortcode is a block-scoped shortcode.
#         """
#         return self._content
    
#     @content.setter
#     def content(self, content: str):
#         # TODO: Do I need to check something (?)
#         content = '' if content is None else content

#         if not PythonValidator.is_string(content):
#             raise Exception('The "content" parameter provided is not a valid string.')
        
#         self._content = content

#     @property
#     def previous_start_word_index(self):
#         """
#         The index, obtained from the whole text of the segment, of the
#         word that is inmediately before the shortcode start tag.
#         """
#         return self._previous_start_word_index
    
#     @previous_start_word_index.setter
#     def previous_start_word_index(self, previous_start_word_index: int):
#         if previous_start_word_index is None:
#             self._previous_start_word_index = previous_start_word_index
#             # TODO: Sorry, fix this
#             return

#         if not NumberValidator.is_number_between(LOWER_INDEX, UPPER_INDEX):
#             raise Exception('The "previous_start_word_index" parameter must be a number in the range [{LOWER_INDEX}, {UPPER_INDEX}].')
        
#         self._previous_start_word_index = previous_start_word_index

#     @property
#     def previous_end_word_index(self):
#         """
#         The index, obtained from the whole text of the segment, of the
#         word that is inmediately before the shortcode end tag.
#         """
#         return self._previous_end_word_index
    
#     @previous_end_word_index.setter
#     def previous_end_word_index(self, previous_end_word_index: int):
#         if previous_end_word_index is None:
#             self._previous_end_word_index = None
#             # TODO: Sorry, fix this
#             return

#         if not NumberValidator.is_number_between(LOWER_INDEX, UPPER_INDEX):
#             raise Exception('The "_previous_end_word_index" parameter must be a number in the range [{LOWER_INDEX}, {UPPER_INDEX}].')
        
#         self._previous_end_word_index = previous_end_word_index

#     def __calculate_start_and_duration(self, transcription: AudioTranscription):
#         """
#         Processes this shortcode 'start' and 'duration' fields by using
#         the 'transcription' if needed (if 'start' and 'duration' fields
#         are not numbers manually set by the user in the shortcode when
#         written).

#         This will consider the current 'start' and 'duration' strategy
#         and apply them to the words related to the shortcode to obtain
#         the real 'start' and 'duration' number values.
#         """
#         if PythonValidator.is_instance(self.start, ShortcodeStart):
#             # TODO: What if single shortcode with no next word (?)
#             if self.type == ShortcodeTagType.SIMPLE:
#                 if self.start == ShortcodeStart.BETWEEN_WORDS:
#                     self.start = (transcription.words[self._previous_start_word_index].start + transcription.words[self._previous_start_word_index + 1].start) / 2
#                 # TODO: What about block-scoped value when simple type (?)
#             else:
#                 if self.start == ShortcodeStart.START_OF_FIRST_SHORTCODE_CONTENT_WORD:
#                     self.start = transcription.words[self._previous_start_word_index + 1].start
#                 elif self.start == ShortcodeStart.MIDDLE_OF_FIRST_SHORTCODE_CONTENT_WORD:
#                     self.start = (transcription.words[self._previous_start_word_index + 1].start + transcription.words[self._previous_start_word_index + 1].end) / 2
#                 elif self.start == ShortcodeStart.END_OF_FIRST_SHORTCODE_CONTENT_WORD:
#                     self.start = transcription.words[self._previous_start_word_index + 1].end
#                 # TODO: What about simple value when block-scoped type (?)

#         if PythonValidator.is_instance(self.duration, ShortcodeDuration):
#             if self.type == ShortcodeTagType.SIMPLE:
#                 if self.duration == ShortcodeDuration.FILE_DURATION:
#                     # This duration must be set when the file is ready, so 
#                     # we use a number value out of limits to flag it
#                     self.duration = FILE_DURATION
#             else:
#                 if self.duration == ShortcodeDuration.SHORTCODE_CONTENT:
#                     self.duration = transcription.words[self.previous_end_word_index].end - transcription.words[self.previous_start_word_index + 1].start

#     def to_enhancement_element(self, transcription: AudioTranscription):
#         """
#         Turns the current shortcode to an EnhancementElement by using
#         the provided 'transcription' and using its words to set the
#         actual 'start' and 'duration' fields according the narration.

#         The provided 'transcription' could be not needed if the segment
#         is not narrated and 'start' and 'duration' fields are manually
#         set by the user in the shortcode.
#         """
#         if self.type == ShortcodeTagType.SIMPLE and self.previous_start_word_index is None:
#             raise Exception(f'Found {ShortcodeTagType.SIMPLE.value} shortcode without "previous_start_word_index".')
        
#         if self.type == ShortcodeTagType.BLOCK and (self.previous_start_word_index is None or self.previous_end_word_index is None):
#             raise Exception(f'Found {ShortcodeTagType.BLOCK.value} shortcode without "previous_start_word_index" or "previous_end_word_index".')
        
#         self.__calculate_start_and_duration(transcription)

#         # TODO: Build it
#         enhancement_element = EnhancementElement.get_class_from_type(self.tag)(self.tag, self.start, self.duration, self.keywords, self.url, self.filename, self.mode)

#         # TODO: Remove this below if the code above is working
#         # if self.tag == ShortcodeTagType.MEME:
#         #     enhancement_element = MemeEnhancementElement(self.tag, self.start, self.duration, self.keywords, self.url, self.filename, self.mode)
#         # elif self.tag == ShortcodeTagType.SOUND:
#         #     enhancement_element = SoundEnhancementElement(self.tag, self.start, self.duration, self.keywords, self.url, self.filename, self.mode)
#         # elif self.tag == ShortcodeTagType.IMAGE:
#         #     enhancement_element = ImageEnhancementElement(self.tag, self.start, self.duration, self.keywords, self.url, self.filename, self.mode)
#         # elif self.tag == ShortcodeTagType.STICKER:
#         #     enhancement_element = StickerEnhancementElement(self.tag, self.start, self.duration, self.keywords, self.url, self.filename, self.mode)
#         # elif self.tag == ShortcodeTagType.GREEN_SCREEN:
#         #     enhancement_element = GreenscreenEnhancementElement(self.tag, self.start, self.duration, self.keywords, self.url, self.filename, self.mode)
#         # elif self.tag == ShortcodeTagType.EFFECT:
#         #     enhancement_element = EffectEnhancementElement(self.tag, self.start, self.duration, self.keywords, self.url, self.filename, self.mode)
#         # else:
#         #     raise Exception(f'No valid shortcode "{self.tag}" type provided.')
#         #     # TODO: Implement the other EnhancementElements
#         #     enhancement_element = EnhancementElement(self.tag, self.start, self.duration, self.keywords, self.url, self.filename, self.mode)

#         return enhancement_element