from __future__ import annotations

import logging
import wx
import asyncio
import output
from mysc.thread_utils import call_threaded
import widgetUtils
from extra.autocompletionUsers import completion
from wxUI.dialogs.blueski.showUserProfile import ShowUserProfileDialog
from typing import Any
import languageHandler  # Ensure _() injection

logger = logging.getLogger(__name__)


class Handler:
    """Handler for Bluesky integration: creates minimal buffers."""

    def __init__(self):
        super().__init__()
        self.menus = dict(
            # Application menu
            updateProfile="HIDE",
            menuitem_search=_("&Search"),
            lists="HIDE",
            manageAliases="HIDE",
            # Item menu
            compose=_("&Post"),
            reply=_("Re&ply"),
            share=_("&Boost"),
            fav=_("&Add to favorites"),
            unfav="HIDE",
            view=_("&Show post"),
            view_conversation=_("View conversa&tion"),
            ocr=_("&OCR"),
            delete=_("&Delete"),
            # User menu
            follow=_("&Actions..."),
            timeline=_("&View timeline..."),
            dm=_("Direct me&ssage"),
            addAlias="HIDE",
            addToList="HIDE",
            removeFromList="HIDE",
            details=_("S&how user profile"),
            favs="HIDE",
            # Buffer menu
            community_timeline="HIDE",
            filter="HIDE",
            manage_filters="HIDE",
        )
        self.item_menu = _("&Post")

    def create_buffers(self, session, createAccounts=True, controller=None):
        name = session.get_name()
        if createAccounts:
            from pubsub import pub
            pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=session.logged)
        
        if not session.logged:
            logger.debug(f"Session {session.session_id} is not logged in, skipping timeline buffer creation.")
            return
        if name not in controller.accounts:
            controller.accounts.append(name)

        root_position = controller.view.search(name, name)
        from pubsub import pub
        # Home (Following-only timeline - reverse-chronological)
        pub.sendMessage(
            "createBuffer",
            buffer_type="following_timeline",
            session_type="blueski",
            buffer_title=_("Home"),
            parent_tab=root_position,
            start=True,
            kwargs=dict(parent=controller.view.nb, name="following_timeline", session=session, sound="tweet_received.ogg")
        )
        # Discover timeline
        pub.sendMessage(
            "createBuffer",
            buffer_type="home_timeline",
            session_type="blueski",
            buffer_title=_("Discover"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="home_timeline", session=session, sound="tweet_received.ogg")
        )
        # Mentions (replies, mentions, quotes)
        pub.sendMessage(
            "createBuffer",
            buffer_type="MentionsBuffer",
            session_type="blueski",
            buffer_title=_("Mentions"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="mentions", session=session, sound="mention_received.ogg")
        )
        # Chats
        pub.sendMessage(
            "createBuffer",
            buffer_type="ConversationListBuffer",
            session_type="blueski",
            buffer_title=_("Chats"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="direct_messages", session=session, sound="dm_received.ogg")
        )
        # Notifications
        pub.sendMessage(
            "createBuffer",
            buffer_type="notifications",
            session_type="blueski",
            buffer_title=_("Notifications"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="notifications", session=session, sound="new_event.ogg")
        )
        # Sent posts
        pub.sendMessage(
            "createBuffer",
            buffer_type="SentBuffer",
            session_type="blueski",
            buffer_title=_("Sent"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="sent", session=session, sound="tweet_received.ogg")
        )
        # Likes
        pub.sendMessage(
            "createBuffer",
            buffer_type="likes",
            session_type="blueski",
            buffer_title=_("Likes"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="likes", session=session, sound="favourite.ogg")
        )
        # Followers
        pub.sendMessage(
            "createBuffer",
            buffer_type="FollowersBuffer",
            session_type="blueski",
            buffer_title=_("Followers"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="followers", session=session, sound="update_followers.ogg")
        )
        # Followings (Users you follow)
        pub.sendMessage(
            "createBuffer",
            buffer_type="FollowingBuffer",
            session_type="blueski",
            buffer_title=_("Following"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="following", session=session, sound="update_followers.ogg")
        )
        # Blocks
        pub.sendMessage(
            "createBuffer",
            buffer_type="BlocksBuffer",
            session_type="blueski",
            buffer_title=_("Blocked Users"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="blocked", session=session)
        )

        # Timelines container
        pub.sendMessage(
            "createBuffer",
            buffer_type="EmptyBuffer",
            session_type="base",
            buffer_title=_("Timelines"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="timelines", account=name)
        )
        timelines_position = controller.view.search("timelines", name)

        # Searches container (Bluesky supports search buffers)
        pub.sendMessage(
            "createBuffer",
            buffer_type="EmptyBuffer",
            session_type="base",
            buffer_title=_("Searches"),
            parent_tab=root_position,
            start=False,
            kwargs=dict(parent=controller.view.nb, name="searches", account=name)
        )
        searches_position = controller.view.search("searches", name)

        # Saved searches
        try:
            searches = session.settings["other_buffers"].get("searches")
            if searches is None:
                searches = []
            if isinstance(searches, str):
                searches = [s for s in searches.split(",") if s]
            for query in searches:
                buffer_name = f"search_{query[:20]}"
                title = _("Search: {query}").format(query=query)
                pub.sendMessage(
                    "createBuffer",
                    buffer_type="SearchBuffer",
                    session_type="blueski",
                    buffer_title=title,
                    parent_tab=searches_position,
                    start=False,
                    kwargs=dict(parent=controller.view.nb, name=buffer_name, session=session, query=query, sound="search_updated.ogg")
                )
        except Exception as e:
            logger.error("Failed to restore Bluesky search buffers: %s", e)

        # Saved user timelines
        try:
            timelines = session.settings["other_buffers"].get("timelines")
            if timelines is None:
                timelines = []
            if isinstance(timelines, str):
                timelines = [t for t in timelines.split(",") if t]
            for actor in timelines:
                handle = actor
                try:
                    if isinstance(actor, str) and actor.startswith("did:"):
                        profile = session.get_profile(actor)
                        if profile:
                            def g(obj, key, default=None):
                                if isinstance(obj, dict):
                                    return obj.get(key, default)
                                return getattr(obj, key, default)
                            handle = g(profile, "handle") or actor
                except Exception:
                    handle = actor
                title = _("Timeline for {user}").format(user=handle)
                pub.sendMessage(
                    "createBuffer",
                    buffer_type="UserTimeline",
                    session_type="blueski",
                    buffer_title=title,
                    parent_tab=timelines_position,
                    start=False,
                    kwargs=dict(parent=controller.view.nb, name=f"{handle}-timeline", session=session, actor=actor, handle=handle, sound="tweet_timeline.ogg")
                )
        except Exception as e:
            logger.error("Failed to restore Bluesky timeline buffers: %s", e)

        # Saved followers/following timelines
        try:
            followers = session.settings["other_buffers"].get("followers_timelines")
            if followers is None:
                followers = []
            if isinstance(followers, str):
                followers = [t for t in followers.split(",") if t]
            for actor in followers:
                handle = actor
                try:
                    if isinstance(actor, str) and actor.startswith("did:"):
                        profile = session.get_profile(actor)
                        if profile:
                            def g(obj, key, default=None):
                                if isinstance(obj, dict):
                                    return obj.get(key, default)
                                return getattr(obj, key, default)
                            handle = g(profile, "handle") or actor
                except Exception:
                    handle = actor
                own_actor = session.db.get("user_id") or session.db.get("user_name")
                own_handle = session.db.get("user_name")
                if actor == own_actor or (own_handle and actor == own_handle) or (handle and own_handle and handle == own_handle):
                    continue
                title = _("Followers for {user}").format(user=handle)
                pub.sendMessage(
                    "createBuffer",
                    buffer_type="FollowersBuffer",
                    session_type="blueski",
                    buffer_title=title,
                    parent_tab=timelines_position,
                    start=False,
                    kwargs=dict(parent=controller.view.nb, name=f"{handle}-followers", session=session, actor=actor, handle=handle, sound="new_event.ogg")
                )
        except Exception as e:
            logger.error("Failed to restore Bluesky followers buffers: %s", e)

        try:
            following = session.settings["other_buffers"].get("following_timelines")
            if following is None:
                following = []
            if isinstance(following, str):
                following = [t for t in following.split(",") if t]
            for actor in following:
                handle = actor
                try:
                    if isinstance(actor, str) and actor.startswith("did:"):
                        profile = session.get_profile(actor)
                        if profile:
                            def g(obj, key, default=None):
                                if isinstance(obj, dict):
                                    return obj.get(key, default)
                                return getattr(obj, key, default)
                            handle = g(profile, "handle") or actor
                except Exception:
                    handle = actor
                own_actor = session.db.get("user_id") or session.db.get("user_name")
                own_handle = session.db.get("user_name")
                if actor == own_actor or (own_handle and actor == own_handle) or (handle and own_handle and handle == own_handle):
                    continue
                title = _("Following for {user}").format(user=handle)
                pub.sendMessage(
                    "createBuffer",
                    buffer_type="FollowingBuffer",
                    session_type="blueski",
                    buffer_title=title,
                    parent_tab=timelines_position,
                    start=False,
                    kwargs=dict(parent=controller.view.nb, name=f"{handle}-following", session=session, actor=actor, handle=handle, sound="new_event.ogg")
                )
        except Exception as e:
            logger.error("Failed to restore Bluesky following buffers: %s", e)

        # Start the background poller for real-time-like updates
        try:
            session.start_streaming()
        except Exception as e:
            logger.error("Failed to start Bluesky streaming for session %s: %s", name, e)

    def start_buffer(self, controller, buffer):
        """Start a newly created Bluesky buffer."""
        try:
            if hasattr(buffer, "start_stream"):
                buffer.start_stream(mandatory=True, play_sound=False)
            # Enable periodic auto-refresh to simulate real-time updates
            if hasattr(buffer, "enable_auto_refresh"):
                buffer.enable_auto_refresh()
        finally:
            # Ensure we won't try to start it again
            try:
                buffer.needs_init = False
            except Exception:
                pass

    def account_settings(self, buffer, controller):
        """Open a minimal account settings dialog for Bluesky."""
        try:
            current_mode = None
            try:
                current_mode = buffer.session.settings["general"].get("boost_mode")
            except Exception:
                current_mode = None
            ask_default = True if current_mode in (None, "ask") else False

            from wxUI.dialogs.blueski.configuration import AccountSettingsDialog
            from .templateEditor import EditTemplate
            dlg = AccountSettingsDialog(controller.view, ask_before_boost=ask_default)
            try:
                if buffer.session.settings.get("templates") is None:
                    buffer.session.settings["templates"] = {}
                templates_cfg = buffer.session.settings.get("templates", {})
                template_state = {
                    "post": templates_cfg.get("post", "$display_name, $reply_to$safe_text $image_descriptions $date."),
                    "person": templates_cfg.get("person", "$display_name (@$screen_name). $followers followers, $following following, $posts posts. Joined $created_at."),
                    "notification": templates_cfg.get("notification", "$display_name $text, $date"),
                }
                dlg.set_template_labels(template_state["post"], template_state["person"], template_state["notification"])

                def edit_post_template(*args, **kwargs):
                    control = EditTemplate(template=template_state["post"], type="post")
                    result = control.run_dialog()
                    if result:
                        buffer.session.settings["templates"]["post"] = result
                        buffer.session.settings.write()
                        template_state["post"] = result
                        dlg.set_template_labels(template_state["post"], template_state["person"], template_state["notification"])

                def edit_person_template(*args, **kwargs):
                    control = EditTemplate(template=template_state["person"], type="person")
                    result = control.run_dialog()
                    if result:
                        buffer.session.settings["templates"]["person"] = result
                        buffer.session.settings.write()
                        template_state["person"] = result
                        dlg.set_template_labels(template_state["post"], template_state["person"], template_state["notification"])

                def edit_notification_template(*args, **kwargs):
                    control = EditTemplate(template=template_state["notification"], type="notification")
                    result = control.run_dialog()
                    if result:
                        buffer.session.settings["templates"]["notification"] = result
                        buffer.session.settings.write()
                        template_state["notification"] = result
                        dlg.set_template_labels(template_state["post"], template_state["person"], template_state["notification"])

                widgetUtils.connect_event(dlg.template_post, widgetUtils.BUTTON_PRESSED, edit_post_template)
                widgetUtils.connect_event(dlg.template_person, widgetUtils.BUTTON_PRESSED, edit_person_template)
                widgetUtils.connect_event(dlg.template_notification, widgetUtils.BUTTON_PRESSED, edit_notification_template)
            except Exception as e:
                logger.error("Failed to init Bluesky templates editor: %s", e)
            resp = dlg.ShowModal()
            if resp == wx.ID_OK:
                vals = dlg.get_values()
                boost_mode = "ask" if vals.get("ask_before_boost") else "direct"
                try:
                    buffer.session.settings["general"]["boost_mode"] = boost_mode
                    buffer.session.settings.write()
                except Exception as e:
                    logger.error("Failed to persist Bluesky boost_mode setting: %s", e)
            dlg.Destroy()
        except Exception as e:
            logger.error("Error opening Bluesky account settings dialog: %s", e)

    def user_details(self, buffer):
        """Show user profile dialog for the selected user/post."""
        session = getattr(buffer, "session", None)
        if not session:
            output.speak(_("No active session to view user details."), True)
            return

        item = buffer.get_item() if hasattr(buffer, "get_item") else None
        if not item:
            output.speak(_("No user selected or identified to view details."), True)
            return

        def g(obj, key, default=None):
            if isinstance(obj, dict):
                return obj.get(key, default)
            return getattr(obj, key, default)

        user_ident = None

        # If we're in a user list, the item itself is the user profile dict/model.
        if g(item, "did") or g(item, "handle"):
            user_ident = g(item, "did") or g(item, "handle")
        else:
            author = g(item, "author")
            if not author:
                post = g(item, "post") or g(item, "record")
                author = g(post, "author") if post else None
            if author:
                user_ident = g(author, "did") or g(author, "handle")

        if not user_ident:
            output.speak(_("No user selected or identified to view details."), True)
            return

        parent = getattr(buffer, "buffer", None) or wx.GetApp().GetTopWindow()
        dialog = ShowUserProfileDialog(parent, session, user_ident)
        dialog.ShowModal()
        dialog.Destroy()

    async def handle_action(self, action_name: str, user_id: str, payload: dict[str, Any]) -> dict[str, Any] | None:
        logger.debug("handle_action stub: %s %s %s", action_name, user_id, payload)
        return None

    async def handle_message_command(self, command: str, user_id: str, message_id: str, payload: dict[str, Any]) -> dict[str, Any] | None:
        logger.debug("handle_message_command stub: %s %s %s %s", command, user_id, message_id, payload)
        return None

    async def handle_user_command(self, command: str, user_id: str, target_user_id: str, payload: dict[str, Any]) -> dict[str, Any] | None:
        logger.debug("handle_user_command stub: %s %s %s %s", command, user_id, target_user_id, payload)
        return None

    def add_to_favourites(self, buffer):
        """Standard action for Alt+Win+F"""
        if hasattr(buffer, "add_to_favorites"):
            buffer.add_to_favorites()
        elif hasattr(buffer, "on_like"):
             # Fallback
             buffer.on_like(None)
    
    def remove_from_favourites(self, buffer):
        """Standard action for Alt+Shift+Win+F"""
        if hasattr(buffer, "remove_from_favorites"):
            buffer.remove_from_favorites()
        elif hasattr(buffer, "on_like"):
            buffer.on_like(None)
            
    def follow(self, buffer):
        """Standard action for Ctrl+Win+S - Opens user actions dialog"""
        if not hasattr(buffer, "get_item"):
            return
        session = getattr(buffer, "session", None)
        if not session:
            output.speak(_("No active session."), True)
            return

        item = buffer.get_item()
        if not item:
            return

        def g(obj, key, default=None):
            if isinstance(obj, dict):
                return obj.get(key, default)
            return getattr(obj, key, default)

        users = []
        buffer_type = getattr(buffer, "type", "")

        if buffer_type in ("user", "post_user_list"):
            # User buffer - item is a user object
            handle = g(item, "handle")
            if handle:
                users = [handle]
        elif buffer_type == "notifications":
            # Notification buffer
            author = g(item, "author")
            if author:
                handle = g(author, "handle")
                if handle:
                    users.append(handle)
            # Also check for post author in the notification subject
            record = g(item, "record")
            if record:
                subject = g(record, "subject")
                if subject:
                    subject_author = g(subject, "author")
                    if subject_author:
                        subject_handle = g(subject_author, "handle")
                        if subject_handle and subject_handle not in users:
                            users.append(subject_handle)
        else:
            # Post buffer - extract author and mentioned users
            # Get the actual post (could be nested in "post" key)
            actual_post = g(item, "post", item)
            record = g(actual_post, "record") or {}

            # Extract mentions from facets
            facets = g(record, "facets") or []
            for facet in facets:
                features = g(facet, "features") or []
                for feature in features:
                    ftype = g(feature, "$type") or g(feature, "py_type") or ""
                    if "mention" in ftype.lower():
                        mention_did = g(feature, "did")
                        # We'd need to resolve DID to handle, but for simplicity just skip
                        # The main author will be added below

            # Get the post author
            author = g(actual_post, "author") or g(item, "author")
            if author:
                handle = g(author, "handle")
                if handle and handle not in users:
                    users.insert(0, handle)

        # Ensure we have at least the author if no users found
        if not users:
            author = g(item, "author") or g(g(item, "post"), "author")
            if author:
                handle = g(author, "handle")
                if handle:
                    users = [handle]

        from controller.blueski import userActions as user_actions_controller
        user_actions_controller.userActions(session, users)

    def open_conversation(self, controller, buffer):
        """Standard action for Control+Win+C"""
        # If this is a chat conversation list, open the selected chat
        if buffer.type == "chat" and hasattr(buffer, "view_chat"):
            buffer.view_chat()
            return
        item = buffer.get_item()
        if not item:
            return

        uri = None
        if hasattr(buffer, "get_selected_item_id"):
            uri = buffer.get_selected_item_id()
        if not uri:
            uri = getattr(item, "uri", None) or (item.get("post", {}).get("uri") if isinstance(item, dict) else None)
        if not uri: return
        
        # Buffer Title
        handle = None
        display_name = None
        if hasattr(buffer, "get_selected_item_author_details"):
            details = buffer.get_selected_item_author_details()
            if details:
                handle = details.get("handle")
        if not handle:
            def g(obj, key, default=None):
                if isinstance(obj, dict):
                    return obj.get(key, default)
                return getattr(obj, key, default)
            author = g(item, "author") or g(g(item, "post"), "author")
            if author:
                handle = g(author, "handle")
                display_name = g(author, "displayName") or g(author, "display_name")
        label = handle or display_name or _("Unknown")
        title = _("Conversation with {0}").format(label)
        
        from pubsub import pub
        pub.sendMessage(
            "createBuffer",
            buffer_type="conversation",
            session_type="blueski",
            buffer_title=title,
            parent_tab=controller.view.search(buffer.session.get_name(), buffer.session.get_name()) if hasattr(buffer.session, "get_name") else None,
            start=True,
            kwargs=dict(parent=controller.view.nb, name=title, session=buffer.session, uri=uri, sound="search_updated.ogg")
        )

    def open_timeline(self, controller, buffer, default="posts"):
        if not hasattr(buffer, "get_item"):
            return
        item = buffer.get_item()
        if not item:
            output.speak(_("No user selected."), True)
            return

        def g(obj, key, default=None):
            if isinstance(obj, dict):
                return obj.get(key, default)
            return getattr(obj, key, default)

        users = []
        handle = None
        if hasattr(buffer, "get_selected_item_author_details"):
            details = buffer.get_selected_item_author_details()
            if details:
                handle = details.get("handle") or details.get("did")
        if not handle:
            if g(item, "handle") or g(item, "did"):
                handle = g(item, "handle") or g(item, "did")
            else:
                author = g(item, "author") or g(g(item, "post"), "author")
                if author:
                    handle = g(author, "handle") or g(author, "did")

        if not handle:
            output.speak(_("No user selected."), True)
            return
        users.append(handle)

        # Add mentioned users if available (facets)
        record = g(g(item, "post"), "record") or g(item, "record")
        facets = g(record, "facets", []) if record else []
        handle_cache = {}

        def resolve_handle(did):
            if did in handle_cache:
                return handle_cache[did]
            try:
                profile = buffer.session.get_profile(did)
                if profile:
                    h = g(profile, "handle")
                    if h:
                        handle_cache[did] = h
                        return h
            except Exception:
                pass
            return None

        self_did = buffer.session.db.get("user_id")
        for facet in facets or []:
            features = g(facet, "features", []) or []
            for feat in features:
                ftype = g(feat, "$type") or g(feat, "py_type") or ""
                if "facet#mention" in ftype:
                    did = g(feat, "did")
                    if not did or did == self_did:
                        continue
                    h = resolve_handle(did)
                    if h and h not in users:
                        users.append(h)

        from wxUI.dialogs.mastodon import userTimeline as userTimelineDialog
        dlg = userTimelineDialog.UserTimeline(users=users, default=default)
        try:
            widgetUtils.connect_event(
                dlg.autocompletion,
                widgetUtils.BUTTON_PRESSED,
                lambda *args, **kwargs: completion.autocompletionUsers(dlg, buffer.session.session_id).show_menu("free"),
            )
        except Exception:
            pass
        try:
            if hasattr(dlg, "autocompletion"):
                dlg.autocompletion.Enable(True)
        except Exception:
            pass
        if dlg.ShowModal() != wx.ID_OK:
            dlg.Destroy()
            return

        action = dlg.get_action()
        user = dlg.get_user().strip() or handle
        dlg.Destroy()

        if user.startswith("@"):
            user = user[1:]
        try:
            profile = buffer.session.get_profile(user)
            if profile is None:
                output.speak(_("User not found."), True)
                return
        except Exception:
            pass
        user_payload = {"handle": user}
        if action == "posts":
            result = self.open_user_timeline(main_controller=controller, session=buffer.session, user_payload=user_payload)
        elif action == "followers":
            result = self.open_followers_timeline(main_controller=controller, session=buffer.session, user_payload=user_payload)
        elif action == "following":
            result = self.open_following_timeline(main_controller=controller, session=buffer.session, user_payload=user_payload)
        else:
            return

        if asyncio.iscoroutine(result):
            call_threaded(asyncio.run, result)

    def open_followers_timeline(self, main_controller, session, user_payload=None):
        actor, handle = self._resolve_actor(session, user_payload)
        if not actor:
            output.speak(_("No user selected."), True)
            return
        self._open_user_list(main_controller, session, actor, handle, list_type="followers")

    def open_following_timeline(self, main_controller, session, user_payload=None):
        actor, handle = self._resolve_actor(session, user_payload)
        if not actor:
            output.speak(_("No user selected."), True)
            return
        self._open_user_list(main_controller, session, actor, handle, list_type="following")

    def open_user_timeline(self, main_controller, session, user_payload=None):
        """Open posts timeline for a user (Alt+Win+I)."""
        actor, handle = self._resolve_actor(session, user_payload)
        if not actor:
            output.speak(_("No user selected."), True)
            return

        actor, handle = self._resolve_actor(session, {"did": actor, "handle": handle})
        if not handle:
            handle = actor

        account_name = session.get_name()
        list_name = f"{handle}-timeline"
        if main_controller.search_buffer(list_name, account_name):
            index = main_controller.view.search(list_name, account_name)
            if index is not None:
                main_controller.view.change_buffer(index)
            return

        title = _("Timeline for {user}").format(user=handle)
        from pubsub import pub
        pub.sendMessage(
            "createBuffer",
            buffer_type="UserTimeline",
            session_type="blueski",
            buffer_title=title,
            parent_tab=main_controller.view.search("timelines", account_name),
            start=True,
            kwargs=dict(parent=main_controller.view.nb, name=list_name, session=session, actor=actor, handle=handle, sound="tweet_timeline.ogg")
        )
        try:
            timelines = session.settings["other_buffers"].get("timelines")
            if timelines is None:
                timelines = []
            if isinstance(timelines, str):
                timelines = [t for t in timelines.split(",") if t]
            key = actor or handle
            if key in timelines:
                from wxUI import commonMessageDialogs
                commonMessageDialogs.timeline_exist()
                return
            if key:
                timelines.append(key)
                session.settings["other_buffers"]["timelines"] = timelines
                session.settings.write()
        except Exception as e:
            logger.error("Failed to persist Bluesky timeline buffer: %s", e)

    def _resolve_actor(self, session, user_payload):
        def g(obj, key, default=None):
            if isinstance(obj, dict):
                return obj.get(key, default)
            return getattr(obj, key, default)

        actor = None
        handle = None
        if user_payload:
            actor = g(user_payload, "did") or g(user_payload, "handle")
            handle = g(user_payload, "handle") or g(user_payload, "did")
        if isinstance(actor, str):
            actor = actor.strip()
            if actor.startswith("@"):
                actor = actor[1:]
        if isinstance(handle, str):
            handle = handle.strip()
            if handle.startswith("@"):
                handle = handle[1:]
        # Resolve handle -> DID when possible, and keep handle for titles
        try:
            if isinstance(actor, str) and not actor.startswith("did:"):
                profile = session.get_profile(actor)
                if profile:
                    did = g(profile, "did")
                    if did:
                        actor = did
                    if not handle:
                        handle = g(profile, "handle")
        except Exception:
            pass

        if not actor:
            actor = session.db.get("user_id") or session.db.get("user_name")
            handle = session.db.get("user_name") or actor

        if not handle and isinstance(actor, str):
            try:
                if actor.startswith("did:"):
                    profile = session.get_profile(actor)
                    if profile:
                        handle = g(profile, "handle")
            except Exception:
                pass

        return actor, handle

    def _open_user_list(self, main_controller, session, actor, handle, list_type):
        account_name = session.get_name()
        if not handle:
            handle = actor
        own_actor = session.db.get("user_id") or session.db.get("user_name")
        own_handle = session.db.get("user_name")
        if actor == own_actor or (own_handle and actor == own_handle) or (handle and own_handle and handle == own_handle):
            name = "followers" if list_type == "followers" else "following"
            try:
                stored = session.settings["other_buffers"].get("followers_timelines" if list_type == "followers" else "following_timelines") or []
                if isinstance(stored, str):
                    stored = [t for t in stored.split(",") if t]
                if actor in stored:
                    stored.remove(actor)
                    session.settings["other_buffers"]["followers_timelines" if list_type == "followers" else "following_timelines"] = stored
                    session.settings.write()
            except Exception:
                pass
            index = main_controller.view.search(name, account_name)
            if index is not None:
                main_controller.view.change_buffer(index)
                return
        list_name = f"{handle}-{list_type}"
        if main_controller.search_buffer(list_name, account_name):
            index = main_controller.view.search(list_name, account_name)
            if index is not None:
                main_controller.view.change_buffer(index)
            return

        settings_key = "followers_timelines" if list_type == "followers" else "following_timelines"
        try:
            stored = session.settings["other_buffers"].get(settings_key)
            if stored is None:
                stored = []
            if isinstance(stored, str):
                stored = [t for t in stored.split(",") if t]
            key = actor or handle
            if key in stored:
                from wxUI import commonMessageDialogs
                commonMessageDialogs.timeline_exist()
                return
        except Exception:
            stored = None

        title = _("Followers for {user}").format(user=handle) if list_type == "followers" else _("Following for {user}").format(user=handle)
        from pubsub import pub
        pub.sendMessage(
            "createBuffer",
            buffer_type="FollowersBuffer" if list_type == "followers" else "FollowingBuffer",
            session_type="blueski",
            buffer_title=title,
            parent_tab=main_controller.view.search("timelines", account_name),
            start=True,
            kwargs=dict(parent=main_controller.view.nb, name=list_name, session=session, actor=actor, handle=handle, sound="new_event.ogg")
        )
        try:
            if stored is None:
                stored = session.settings["other_buffers"].get(settings_key) or []
                if isinstance(stored, str):
                    stored = [t for t in stored.split(",") if t]
            key = actor or handle
            if key:
                stored.append(key)
                session.settings["other_buffers"][settings_key] = stored
                session.settings.write()
        except Exception as e:
            logger.error("Failed to persist Bluesky %s buffer: %s", list_type, e)

    def delete(self, buffer, controller):
        """Standard action for delete key / menu item"""
        item = buffer.get_item()
        if not item: return

        uri = getattr(item, "uri", None) or (item.get("post", {}).get("uri") if isinstance(item, dict) else None)
        if not uri: return

        import wx
        if wx.MessageBox(_("Are you sure you want to delete this post?"), _("Delete post"), wx.YES_NO | wx.ICON_QUESTION) == wx.YES:
            if buffer.session.delete_post(uri):
                import output
                output.speak(_("Post deleted."))
                # Refresh buffer
                if hasattr(buffer, "start_stream"):
                    buffer.start_stream(mandatory=True, play_sound=False)
            else:
                import output
                output.speak(_("Failed to delete post."))

    def search(self, controller, session, value=""):
        """Open search dialog and create search buffer for results."""
        dlg = wx.TextEntryDialog(
            controller.view,
            _("Enter search term:"),
            _("Search Bluesky"),
            value
        )
        if dlg.ShowModal() != wx.ID_OK:
            dlg.Destroy()
            return

        query = dlg.GetValue().strip()
        dlg.Destroy()

        if not query:
            return

        # Create unique buffer name for this search
        buffer_name = f"search_{query[:20]}"
        account_name = session.get_name()

        # Check if buffer already exists
        existing = controller.search_buffer(buffer_name, account_name)
        if existing:
            # Navigate to existing buffer
            index = controller.view.search(buffer_name, account_name)
            if index is not None:
                controller.view.change_buffer(index)
                # Refresh search
                existing.search_query = query
                existing.start_stream(mandatory=True, play_sound=False)
            return

        # Create new search buffer
        title = _("Search: {query}").format(query=query)
        from pubsub import pub
        pub.sendMessage(
            "createBuffer",
            buffer_type="SearchBuffer",
            session_type="blueski",
            buffer_title=title,
            parent_tab=controller.view.search("searches", account_name),
            start=True,
            kwargs=dict(
                parent=controller.view.nb,
                name=buffer_name,
                session=session,
                query=query,
                sound="search_updated.ogg"
            )
        )

        # Save search to settings for persistence
        try:
            searches = session.settings["other_buffers"].get("searches")
            if searches is None:
                searches = []
            if isinstance(searches, str):
                searches = [s for s in searches.split(",") if s]
            if query not in searches:
                searches.append(query)
                session.settings["other_buffers"]["searches"] = searches
                session.settings.write()
        except Exception as e:
            logger.error("Failed to save search to settings: %s", e)
