"""
Helper functions to interact with various WhatsApp Web UI components using Playwright.

Conventions:
- `page`: refers to `playwright.sync_api.Page` instance.
- All other elements returned are of the type `Locator`.
- Utility functions are written to extract attributes or recognize content like images, videos, or quoted messages.
"""
import re
import logging
from typing import Union, Optional

from playwright.async_api import ElementHandle, Locator, Page

from src.Interfaces.web_ui_selector import WebUISelectorCapable


class WebSelectorConfig(WebUISelectorCapable):
    """Generic Custom Class , Different from every Platform"""

    def __init__(self, page: Page, log : logging.Logger) -> None:
        super().__init__(page=page,log=log)
        if self.page is None:
            raise ValueError("page must not be None")

    # Todo , Adding all the new Functions from Child to here.
    def chat_list(self) -> Locator:
        """Returns the chat list grid locator on the main UI."""
        return self.page.get_by_role("grid", name=re.compile("chat list", re.I))

    def message_chat_panel(self) -> Locator:
        """ Gives the message container panel"""
        return self.page.locator("div[id='main']").get_by_role("application").first

    def new_chat_chat_list_panel(self) -> Locator:
        """ Return the locator for the new chat on the chat list upper panel"""
        return self.page.get_by_role("button", name=re.compile("new chat", re.I)).first

    def searchBox_chatList_panel(self) -> Locator:
        """Returns the search box on the chat list panel"""
        return self.page.get_by_role("textbox", name=re.compile("search input textbox", re.I)).first

    def message_box(self) -> Locator:
        """Message Input box on the message panel"""
        return self.page.locator("footer").get_by_role("textbox").first

    def wa_icon(self) -> Locator:
        """WhatsApp icon locator"""
        return self.page.get_by_role("heading", name="WhatsApp").first

    def chat_list_filters_ALL(self) -> Locator:
        """Return the chat list filter : ALL """
        return self.page.locator("#all-filter")

    def chat_list_filters_Unread(self) -> Locator:
        """Return the chat list filter : Unread"""
        return self.page.locator("#unread-filter")

    def chat_list_filters_favorites(self) -> Locator:
        """Return the chat list filter : Favorites"""
        return self.page.locator("#favorites-filter")

    def chat_list_filters_groups(self) -> Locator:
        """Return the chat list filter : Groups"""
        return self.page.locator("#group-filter")

    async def total_chats(self) -> int:
        """Returns the total number of chats visible in the chat list."""
        element = self.chat_list()
        attr = await element.get_attribute("aria-rowcount")
        return int(attr) if attr else 0

    def chat_items(self) -> Optional[Locator]:
        """Returns a locator for all individual chat items (buttons) in the list."""
        row_locator = self.chat_list().get_by_role("row")
        list_locator = self.chat_list().get_by_role("listitem")
        if row_locator:
            return row_locator
        elif list_locator:
            return list_locator
        return None

    @staticmethod
    async def getChat_low_Quality_Img(chat: Union[ElementHandle, Locator]) -> str:
        """Extracts the low-quality image (thumbnail) from a chat preview item."""
        if isinstance(chat, Locator):
            chat = await chat.element_handle(timeout=1001)
            if chat is None:
                return ""

        imgs = await chat.query_selector_all("img[src]")
        if imgs:
            img_el = imgs[0]
            if await img_el.is_visible():
                src = await img_el.get_attribute("src")
                return src or ""
        return ""

    @staticmethod
    async def getChatName(chat: Union[ElementHandle, Locator]) -> str:
        """Returns the primary chat name (first span[title]) or empty string."""
        if isinstance(chat, Locator):
            chat = await chat.element_handle(timeout=1001)
            if chat is None:
                return ""

        if await WebSelectorConfig.is_community(chat):
            spans = await chat.query_selector_all("span[title]")
            if len(spans) > 1:
                return await spans[1].get_attribute("title") or ""
            return ""

        span = await chat.query_selector("span[title]")
        if span:
            return await span.get_attribute("title") or ""
        return ""

    @staticmethod
    async def is_community(chat: Union[ElementHandle, Locator]) -> bool:
        """
        If this chat item has the 'default-community-refreshed' icon,
        return True.
        """
        if isinstance(chat, Locator):
            chat = await chat.element_handle(timeout=1001)
            if chat is None:
                return False

        icon = await chat.query_selector("span[data-icon='default-community-refreshed']")
        if icon and await icon.is_visible():
            return True
        return False

    def Profile_header(self) -> Locator:
        """
        Returns the profile header button locator used to open contact details.
        Used when a chat is opened and the top bar includes profile/name/media access.
        """
        return self.page.locator("header").get_by_role("button", name=re.compile("profile details", re.I))

    def qr_canvas(self) -> Locator:
        """Returns the QR canvas image for login."""
        return self.page.get_by_role("img", name=re.compile(r"scan.*qr", re.I))

    # -------------------- Sidebar Navigation -------------------- #

    def _side_Bar_chats(self) -> Locator:
        """Returns the sidebar button locator for 'Chats'."""
        return self.page.locator("header").first.get_by_role("button", name=re.compile("chats", re.I))

    def _side_Bar_status(self) -> Locator:
        """Returns the sidebar button locator for 'Status Updates'."""
        return self.page.locator("header").first.get_by_role("button", name=re.compile("updates in status", re.I))

    def _side_Bar_channels(self) -> Locator:
        """Returns the sidebar button locator for 'Channels'."""
        return self.page.locator("header").first.get_by_role("button", name=re.compile("channels", re.I))

    def _side_Bar_Communities(self) -> Locator:
        """Returns the sidebar button locator for 'Communities'."""
        return self.page.locator("header").first.get_by_role("button", name=re.compile("communities", re.I))

    # -------------------- Messages Section -------------------- #

    async def messages(self) -> Locator:
        """
        Returns a locator for all messages in the current open chat.
        Each message element has a unique `data-id` and role "row".
        """
        return self.page.locator('[role="row"] div[data-id]')

    async def messages_incoming(self) -> Locator:
        """Filter for the personal | group chat incoming messages"""
        return self.page.locator('[role="row"] div[data-id] .message-in')

    async def messages_outgoing(self) -> Locator:
        """Filter for the personal | group chat outgoing messages"""
        return self.page.locator('[role="row"] div[data-id] .message-out')

    @staticmethod
    async def get_message_text(message_element: Union[ElementHandle, Locator]) -> str:
        """Returns the text content of a message if visible."""
        if isinstance(message_element, Locator):
            message_element = await message_element.element_handle()
            if message_element is None:
                return ""

        # OLD: span.selectable-text.copyable-text (Class 'selectable-text' removed in recent WA updates)
        # NEW: span[data-testid="selectable-text"] OR span.copyable-text
        span = await message_element.query_selector("span[data-testid='selectable-text']")
        if not span:
            # Fallback to inner_text directly if span not found
            return await message_element.inner_text() or ""

        if await span.is_visible():
            text = await span.text_content()
            return text or ""
        return ""

    # def get_message_text(element: Locator | ElementHandle) -> str:
    #     return element.inner_text().strip()

    @staticmethod
    async def is_message_out(message: Union[ElementHandle, Locator]) -> bool:
        """Returns True if the message is outgoing (sent by bot)."""
        if isinstance(message, ElementHandle):
            element = await message.query_selector(".message-out")
        else:
            element = message.locator(".message-out")

        if isinstance(element, Locator):
            return await element.is_visible()
        return element is not None and await element.is_visible()

    @staticmethod
    async def get_dataID(message: Union[ElementHandle, Locator]) -> str:
        """Returns the unique data-id attribute of a message."""
        ID = await message.get_attribute("data-id")
        return ID or ""

    # -------------------- Media Send  -------------------- #
    def plus_rounded_icon(self) -> Locator:
        """
        It is a locator for the plus icon in the message box for opening menu with options like : image , videos ,documents to send
        """
        return self.page.get_by_role("button").locator("span[data-icon]").filter(
            has_text=re.compile("plus-rounded", re.I)).first

    def document(self) -> Locator:
        """Safely locates the 'Document' upload option in the menu"""
        return self.page.get_by_role("button", name="Document").first

    def photos_videos(self) -> Locator:
        """Safely locates the 'Photos & videos' upload option in the menu"""
        return self.page.get_by_role("button", name="Photos & videos").first

    def camera(self) -> Locator:
        """Safely locates the 'Camera' upload option in the menu"""
        return self.page.get_by_role("button", name="Camera").first

    def audio(self) -> Locator:
        """Safely locates the 'Audio' upload option in the menu"""
        return self.page.get_by_role("button", name="Audio").first

    def contact(self) -> Locator:
        """Safely locates the 'Contact' upload option in the menu"""
        return self.page.get_by_role("button", name="Contact").first

    def poll(self) -> Locator:
        """Safely locates the 'Poll' upload option in the menu"""
        return self.page.get_by_role("button", name="Poll").first

    def event(self) -> Locator:
        """Safely locates the 'Event' upload option in the menu"""
        return self.page.get_by_role("button", name="Event").first

    def new_sticker(self) -> Locator:
        """Safely locates the 'New sticker' upload option in the menu"""
        return self.page.get_by_role("button", name="New sticker")

    # -------------------- Message Type Checkers -------------------- #
    @staticmethod
    async def get_mess_pic_url(message: ElementHandle) -> str:
        """Extracts the image URL from an incoming picture message if visible."""
        img_el = await message.query_selector("img")
        if img_el:
            return await img_el.get_attribute("src") or ""
        return ""

    @staticmethod
    async def isReacted(message: Union[ElementHandle, Locator]) -> bool:
        """Check if the message is reacted or not"""
        try:
            if isinstance(message, Locator):
                message = await message.element_handle(timeout=1001)
            btn = await message.query_selector("button[aria-label*='reaction 👍']")
            return await btn.is_visible() if btn else False
        except Exception:
            return False

    async def pic_handle(self, message: ElementHandle) -> bool:
        """Checks if current message contains a picture."""
        pic = self.page.get_by_role("button", name=re.compile("open picture", re.I))
        if pic and await pic.is_visible():
            return True
        pic2 = await message.query_selector("xpath=.//img[contains(@src,'data:image/')]")
        return await pic2.is_visible() if pic2 else False

    @staticmethod
    async def isVideo(message: ElementHandle) -> bool:
        """
        Checks if current message is of type : video
        :param message: ElementHandle
        :return: bool
        """
        video = await message.query_selector(
            "xpath=.//span[@data-icon='media-play' or @data-icon='msg-video']"
        )
        return await video.is_visible() if video else False

    @staticmethod
    async def is_Voice_Message(message: ElementHandle) -> bool:
        voice = await message.query_selector(
            "xpath=.//button[contains(translate(@aria-label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'voice message')]"
        )
        if voice and await voice.is_visible():
            return True
        voice2 = await message.query_selector(
            "xpath=.//span[contains(translate(@data-icon,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'audio-play')]"
        )
        return await voice2.is_visible() if voice2 else False

    @staticmethod
    async def is_gif(message: ElementHandle) -> bool:
        gif_btn = await message.query_selector(
            "xpath=.//div[@role='button' and "
            "contains(translate(@aria-label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'play gif')]"
        )
        if gif_btn and await gif_btn.is_visible():
            return True
        gif2 = await message.query_selector(
            "xpath=.//span[contains(translate(@data-icon,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'media-gif')]"
        )
        return await gif2.is_visible() if gif2 else False

    @staticmethod
    async def is_animated_sticker(message: ElementHandle) -> bool:
        sticker = await message.query_selector(
            "xpath=.//img[contains(translate(@alt,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'animated sticker')]"
        )
        return await sticker.is_visible() if sticker else False

    @staticmethod
    async def is_plain_sticker(message: ElementHandle) -> bool:
        sticker = await message.query_selector(
            "xpath=.//img[contains(translate(@alt,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'sticker with no label')]"
        )
        return await sticker.is_visible() if sticker else False

    @staticmethod
    async def is_lottie_animation_sticker(message: ElementHandle) -> bool:
        el = await message.query_selector(
            "xpath=.//button[contains(translate(@aria-label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'sticker')]//img[contains(@src,'blob:')]"
        )
        return await el.is_visible() if el else False

    @staticmethod
    async def isSticker(message: ElementHandle) -> bool:
        """Returns True if any sticker type is detected using XPath."""
        return (
                await WebSelectorConfig.is_animated_sticker(message)
                or await WebSelectorConfig.is_plain_sticker(message)
                or await WebSelectorConfig.is_lottie_animation_sticker(message)
        )

    # -------------------- Quoted Message Utilities -------------------- #
    @staticmethod
    async def isQuotedText(message: ElementHandle) -> Optional[ElementHandle]:
        """
        Checks if a message is quoting another and returns the quoted-message handle.
        """
        quoted = await message.query_selector("span.quoted-mention")
        return quoted

    @staticmethod
    def get_QuotedText_handle(message: ElementHandle) -> str:
        """Returns the handle for the quoted-mention span inside a quoted message."""
        quoted = WebSelectorConfig.isQuotedText(message)
        return quoted.is_visible() if quoted else ""

    # -- System -- #

    def startup_popup_locator(self) -> Locator:
        """Returns the startup continue popup button locator."""
        return self.page.get_by_role("button", name=re.compile("continue", re.I))

    async def popup2(self):
        """
        2nd Pop up of WhatsApp with message:
        "Your chats and calls are private"
        """
        page = self.page
        button = await page.query_selector("div[data-animate-model-popup] button:text-is('OK')")
        if button:
            try:
                if await button.is_visible():
                    await button.click()
            except Exception as e:
                self.log.error(f"[popup2] Click failed: {e}")

    # ---------------------------- Message Other Option ------------------------------ #
    """
    Options :
    --Group info 
    --select messages
    --Mute notifications
    --Disappearing messages
    --Add to favourite
    --close chat 
    --clear chat 
    --Exit group
    """

    async def group_info(self) -> Optional[ElementHandle]:
        dialog = await self.page.query_selector("div[role='dialog']")
        return await dialog.query_selector("li:has-text('group info')") if dialog else None

    async def select_messages(self) -> Optional[ElementHandle]:
        dialog = await self.page.query_selector("div[role='dialog']")
        return await dialog.query_selector("li:has-text('select messages')") if dialog else None

    async def mute_notifications(self) -> Optional[ElementHandle]:
        dialog = await self.page.query_selector("div[role='dialog']")
        return await dialog.query_selector("li:has-text('mute notifications')") if dialog else None

    async def disappearing_messages(self) -> Optional[ElementHandle]:
        dialog = await self.page.query_selector("div[role='dialog']")
        return await dialog.query_selector("li:has-text('disappearing messages')") if dialog else None

    async def add_to_fav(self) -> Optional[ElementHandle]:
        dialog = await self.page.query_selector("div[role='dialog']")
        return await dialog.query_selector("li:has-text('add to favourites')") if dialog else None

    async def close_chat(self) -> Optional[ElementHandle]:
        dialog = await self.page.query_selector("div[role='dialog']")
        return await dialog.query_selector("li:has-text('close chat')") if dialog else None

    async def clear_chat(self) -> Optional[ElementHandle]:
        dialog = await self.page.query_selector("div[role='dialog']")
        return await dialog.query_selector("li:has-text('clear chat')") if dialog else None
