"""
ScreenTopology — The Instagram HD Map

Pure-data BFS pathfinding between Instagram screen states.
Zero dependencies on device, VLM, Qdrant, or any runtime state.

This is the bot's GPS: it knows HOW to get from screen A to screen B
before the bot starts moving. The GOAP planner consults this map
as its primary routing strategy.
"""

from collections import deque
from typing import Dict, List, Optional, Tuple

from GramAddict.core.goap import ScreenType


class ScreenTopology:
    """
    Topological HD Map of Instagram's screen graph.

    Provides BFS pathfinding between any two ScreenTypes.
    All transitions use the same action string format as
    the TelepathicEngine intent system — no translation needed.
    """

    # ── The Map: ScreenType → {action_string → ScreenType} ──
    # These are structural facts about Instagram's UI, not learned behavior.
    # They survive blank_start because they describe the app's architecture.
    TRANSITIONS: Dict[ScreenType, Dict[str, ScreenType]] = {
        ScreenType.HOME_FEED: {
            "tap explore tab": ScreenType.EXPLORE_GRID,
            "tap profile tab": ScreenType.OWN_PROFILE,
            "tap reels tab": ScreenType.REELS_FEED,
            "tap messages tab": ScreenType.DM_INBOX,
            "tap story ring avatar": ScreenType.STORY_VIEW,
        },
        ScreenType.EXPLORE_GRID: {
            "tap home tab": ScreenType.HOME_FEED,
            "tap profile tab": ScreenType.OWN_PROFILE,
            "tap reels tab": ScreenType.REELS_FEED,
            "view a post": ScreenType.POST_DETAIL,
        },
        ScreenType.REELS_FEED: {
            "tap home tab": ScreenType.HOME_FEED,
            "tap explore tab": ScreenType.EXPLORE_GRID,
            "tap profile tab": ScreenType.OWN_PROFILE,
        },
        ScreenType.OWN_PROFILE: {
            "tap home tab": ScreenType.HOME_FEED,
            "tap explore tab": ScreenType.EXPLORE_GRID,
            "tap reels tab": ScreenType.REELS_FEED,
            "tap following list": ScreenType.FOLLOW_LIST,
        },
        ScreenType.DM_INBOX: {
            "press back": ScreenType.HOME_FEED,
        },
        ScreenType.FOLLOW_LIST: {
            "press back": ScreenType.OWN_PROFILE,
        },
        ScreenType.STORY_VIEW: {
            "press back": ScreenType.HOME_FEED,
        },
        ScreenType.OTHER_PROFILE: {
            "press back": ScreenType.HOME_FEED,
        },
        ScreenType.POST_DETAIL: {
            "tap home tab": ScreenType.HOME_FEED,
            "tap explore tab": ScreenType.EXPLORE_GRID,
            "tap profile tab": ScreenType.OWN_PROFILE,
            "tap reels tab": ScreenType.REELS_FEED,
        },
        ScreenType.UNKNOWN: {
            "tap home tab": ScreenType.HOME_FEED,
        },
    }

    # ── Goal → ScreenType mapping ──
    # Maps natural-language goals to their target screen.
    _GOAL_MAP: Dict[str, ScreenType] = {
        "open home feed": ScreenType.HOME_FEED,
        "open home": ScreenType.HOME_FEED,
        "open explore feed": ScreenType.EXPLORE_GRID,
        "open explore": ScreenType.EXPLORE_GRID,
        "open reels": ScreenType.REELS_FEED,
        "open profile": ScreenType.OWN_PROFILE,
        "learn own profile": ScreenType.OWN_PROFILE,
        "open messages": ScreenType.DM_INBOX,
        "open following list": ScreenType.FOLLOW_LIST,
        "open followers list": ScreenType.FOLLOW_LIST,
        "view a post": ScreenType.POST_DETAIL,
        "open post": ScreenType.POST_DETAIL,
        "open post author profile": ScreenType.OTHER_PROFILE,
        "view the user profile": ScreenType.OTHER_PROFILE,
        "view user profile": ScreenType.OTHER_PROFILE,
        "open user profile": ScreenType.OTHER_PROFILE,
    }

    @classmethod
    def find_route(
        cls, from_screen: ScreenType, to_screen: ScreenType, avoid_actions: set = None
    ) -> Optional[List[Tuple[str, ScreenType]]]:
        """
        BFS shortest path from from_screen to to_screen.

        Returns:
            [] if already there,
            [(action, resulting_screen), ...] for a path,
            None if unreachable.
        """
        if from_screen == to_screen:
            return []

        avoid_actions = avoid_actions or set()

        queue: deque = deque()
        queue.append((from_screen, []))
        visited = {from_screen}

        while queue:
            current, path = queue.popleft()
            transitions = cls.TRANSITIONS.get(current, {})

            for action, next_screen in transitions.items():
                if action in avoid_actions or action.replace(" ", "_") in avoid_actions:
                    continue

                if next_screen == to_screen:
                    return path + [(action, next_screen)]

                if next_screen not in visited:
                    visited.add(next_screen)
                    queue.append((next_screen, path + [(action, next_screen)]))

        return None  # Unreachable

    @classmethod
    def get_transitions(cls, screen: ScreenType) -> Dict[str, ScreenType]:
        """Get all known transitions from a screen."""
        return dict(cls.TRANSITIONS.get(screen, {}))

    @classmethod
    def goal_to_target_screen(cls, goal: str) -> Optional[ScreenType]:
        """Map a goal string to its target ScreenType. Returns None for non-navigation goals."""
        goal_lower = goal.lower().strip()

        # Exact match first
        if goal_lower in cls._GOAL_MAP:
            return cls._GOAL_MAP[goal_lower]

        # Substring match for flexibility
        for key, screen in cls._GOAL_MAP.items():
            if key in goal_lower:
                return screen

        return None

    # ── QNavGraph screen name ↔ ScreenType mapping (SSOT) ──
    SCREEN_NAME_MAP: Dict[str, ScreenType] = {
        "HomeFeed": ScreenType.HOME_FEED,
        "ExploreFeed": ScreenType.EXPLORE_GRID,
        "ReelsFeed": ScreenType.REELS_FEED,
        "OwnProfile": ScreenType.OWN_PROFILE,
        "MessageInbox": ScreenType.DM_INBOX,
        "FollowingList": ScreenType.FOLLOW_LIST,
        "OtherProfile": ScreenType.OTHER_PROFILE,
        "StoriesFeed": ScreenType.HOME_FEED,  # Stories are on home feed
        "SearchFeed": ScreenType.EXPLORE_GRID,  # Search uses explore
        "UNKNOWN": ScreenType.UNKNOWN,
    }

    # ── Reverse map: ScreenType → canonical goal string ──
    _SCREEN_TO_GOAL: Dict[ScreenType, str] = {
        ScreenType.HOME_FEED: "open home feed",
        ScreenType.EXPLORE_GRID: "open explore feed",
        ScreenType.REELS_FEED: "open reels",
        ScreenType.OWN_PROFILE: "open profile",
        ScreenType.DM_INBOX: "open messages",
        ScreenType.FOLLOW_LIST: "open following list",
    }

    @classmethod
    def screen_name_to_goal(cls, screen_name: str) -> str:
        """Convert QNavGraph screen name to GOAP goal string.

        Returns a canonical goal string for known screens,
        or 'navigate to <name>' for unknown ones.
        """
        screen_type = cls.SCREEN_NAME_MAP.get(screen_name)
        if screen_type and screen_type in cls._SCREEN_TO_GOAL:
            return cls._SCREEN_TO_GOAL[screen_type]
        return f"navigate to {screen_name}"

    @classmethod
    def expected_screen_for_action(cls, action: str, from_screen: ScreenType) -> Optional[ScreenType]:
        """What screen should we land on after this action from this screen?

        Used by _execute_action to validate INTERMEDIATE navigation steps.
        Returns None if the action isn't a known transition from this screen.
        """
        transitions = cls.TRANSITIONS.get(from_screen, {})
        return transitions.get(action)

    @classmethod
    def is_structural_action(cls, screen: ScreenType, action: str) -> bool:
        """Check if an action is a structural transition in the HD Map.

        Structural actions must NEVER be aversively learned as traps —
        they are architectural facts about Instagram's UI.
        VLM may fail to find the element, but the route itself is valid.
        """
        transitions = cls.TRANSITIONS.get(screen, {})
        return action in transitions
