"""Enhanced navigation extractor for the code map scanner.

Extracts all navigation types from source files including:
- Direct route pushes (router.push, navigate, href)
- Conditional/ternary navigation (isAdmin ? navigate('/admin') : navigate('/home'))
- Role-based guards (if (!auth) redirect('/login'))
- Callback-context navigation (onSuccess, onError, catch blocks)
- Framework-specific patterns (Next.js redirect, Vue $router, Angular router)
- Modal/state/tab triggers
- Form submit navigation
"""

import re
from dataclasses import dataclass, field


# ---------------------------------------------------------------------------
# NavEntry data structure
# ---------------------------------------------------------------------------

@dataclass
class NavEntry:
    mechanism: str           # "route" | "conditional_route" | "modal" | "state" | "tab" | "redirect" | "outlet"
    to: str | None           # destination URL/route name (None for modals/state)
    fn: str | None           # function/action name (for modal/state)
    condition: str | None    # human-readable condition e.g. "if user is not authenticated"
    trigger: str | None      # "on_success" | "on_error" | "on_click" | "on_submit" | "on_mount" | "always" | None
    selector_hint: str | None  # nearby CSS selector if detectable
    dynamic: bool = False    # True if route has [id] or :param segments


# ---------------------------------------------------------------------------
# Regex patterns — all navigation mechanisms
# ---------------------------------------------------------------------------

# Direct router.push/replace
_ROUTER_PUSH_RE = re.compile(
    r'router\.(push|replace)\s*\(\s*["\']([^"\']{1,120})["\']'
)

# useNavigate() / navigate() — React Router v6
_NAVIGATE_RE = re.compile(
    r'(?<!\$)(?<!\.)(?<![a-zA-Z_$])\bnavigate\s*\(\s*["\']([^"\']{1,120})["\']'
)

# Ternary conditional: condition ? navigate('/a') : navigate('/b')
# Also: condition ? router.push('/a') : router.push('/b')
# Limit condition capture to last 80 chars before the ? to avoid over-capturing
_TERNARY_RE = re.compile(
    r'([a-zA-Z_$][a-zA-Z0-9_$.\s!&|=<>]{1,80})\s*\?\s*(?:navigate|router\.push|router\.replace)\s*\(\s*["\']([^"\']{1,120})["\']'
    r'\s*[^)]*\)\s*:\s*(?:navigate|router\.push|router\.replace)\s*\(\s*["\']([^"\']{1,120})["\']',
    re.MULTILINE,
)

# if-block redirect: if (!isAuth) { navigate('/login') } or if (condition) { router.push(...) }
_IF_BLOCK_RE = re.compile(
    r'if\s*\(([^)]{1,120})\)\s*\{?[^}]{0,200}?(?:navigate|router\.push|router\.replace|redirect)\s*\(\s*["\']([^"\']{1,120})["\']',
    re.DOTALL,
)

# Next.js server-side redirect() / permanentRedirect()
_NEXTJS_REDIRECT_RE = re.compile(
    r'\b(?:redirect|permanentRedirect)\s*\(\s*["\']([^"\']{1,120})["\']'
)

# Next.js middleware: NextResponse.redirect(new URL('/path', ...)) or NextResponse.redirect('/path')
_NEXT_MIDDLEWARE_RE = re.compile(
    r'NextResponse\.redirect\s*\([^)]*["\']([^"\']{1,120})["\']'
)

# Vue Router: this.$router.push('/path') or $router.push({ name: 'route' })
_VUE_ROUTER_RE = re.compile(
    r'\$router\.(?:push|replace)\s*\(\s*["\']([^"\']{1,120})["\']'
    r'|\$router\.(?:push|replace)\s*\(\s*\{[^}]*(?:path|name)\s*:\s*["\']([^"\']{1,120})["\']',
)

# Angular: this.router.navigate(['/path']) or this.router.navigateByUrl('/path')
_ANGULAR_NAVIGATE_RE = re.compile(
    r'\.router\.navigate\s*\(\s*\[\s*["\']([^"\']{1,120})["\']'
    r'|\.router\.navigateByUrl\s*\(\s*["\']([^"\']{1,120})["\']',
)

# <NavLink to="..."> — React Router
_NAVLINK_RE = re.compile(r'<NavLink[^>]+\bto=["\']([^"\']{1,120})["\']')

# <Link href="..."> — Next.js or React Router
_LINK_HREF_RE = re.compile(r'<Link[^>]+\bhref=["\']([^"\']{1,120})["\']')

# <Link to="..."> — React Router Link
_LINK_TO_RE = re.compile(r'<Link[^>]+\bto=["\']([^"\']{1,120})["\']')

# <Route path="..."> — React Router route definitions
_ROUTE_DEF_RE = re.compile(r'<Route[^>]+\bpath=["\']([^"\']{1,120})["\']')

# Callback-context nav: .then/.catch/.finally/onSuccess/onError blocks containing navigate
_CALLBACK_RE = re.compile(
    r'(onSuccess|on_success|onError|on_error|\.catch|\.then|\.finally|'
    r'catch\s*\(|handleSuccess|handleError|afterSubmit)\s*[:\(=>{\s]*'
    r'[^}]{0,300}?(?:navigate|router\.push|router\.replace)\s*\(\s*["\']([^"\']{1,120})["\']',
    re.DOTALL,
)

# Form submit handler navigation
_SUBMIT_NAV_RE = re.compile(
    r'(?:handleSubmit|onSubmit|handleForm|submitForm|handleLogin|handleSignIn)'
    r'[^{]*\{[^}]{0,600}?(?:navigate|router\.push)\s*\(\s*["\']([^"\']{1,120})["\']',
    re.DOTALL,
)

# dispatch(toggleXModal()) — modal triggers
_MODAL_DISPATCH_RE = re.compile(
    r'dispatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*(?:toggle|Toggle|Modal|modal|Open|open|Close|close)[a-zA-Z0-9_$]*)\s*\('
)

# setShowX(true) / setOpenX(true) / setIsXVisible(true) — state-driven overlays
_STATE_RE = re.compile(
    r'set(?:Show|Open|Visible|Is)([A-Z][a-zA-Z0-9]*)\s*\(\s*true\s*\)'
)

# window.open(...) — external tab navigation
_WINDOW_OPEN_RE = re.compile(r'window\.open\s*\(\s*["\']([^"\']{1,200})["\']')

# Dynamic route detection: /path/[id] or /path/:param
_DYNAMIC_ROUTE_RE = re.compile(r'/[a-z0-9_-]+/(?:\[[^\]]+\]|:[a-zA-Z][a-zA-Z0-9_]*)')

# Nearby selector hint — look for data-testid or id near a navigation call
_NEARBY_SELECTOR_RE = re.compile(
    r'(?:data-testid|id)=["\']([^"\']{1,80})["\']'
)


# ---------------------------------------------------------------------------
# Values to skip
# ---------------------------------------------------------------------------
_SKIP_ROUTES = {
    "", "#", "/", ".", "..", "/api", "javascript:void(0)",
    "http://", "https://", "mailto:", "tel:",
}
_SKIP_ROUTE_PREFIXES = ("node_modules", ".css", ".scss", ".png", ".svg",
                         ".jpg", ".ico", "localhost", "#")


def _is_valid_route(path: str) -> bool:
    if not path or not path.startswith("/"):
        return False
    if path in _SKIP_ROUTES:
        return False
    for prefix in _SKIP_ROUTE_PREFIXES:
        if path.startswith(prefix) or prefix in path:
            return False
    return True


def _is_dynamic(path: str) -> bool:
    return bool(_DYNAMIC_ROUTE_RE.search(path))


def _clean_condition(raw: str) -> str:
    """Make a condition string human-readable."""
    c = raw.strip().strip("(").strip(")")
    # Remove leading ! for negation: !isAuth → "user is NOT authenticated"
    negated = False
    if c.startswith("!"):
        negated = True
        c = c[1:].strip()
    # Clean up common camelCase patterns
    c = c.replace("isAuthenticated", "user is authenticated")
    c = c.replace("isLoggedIn", "user is logged in")
    c = c.replace("isAdmin", "user is admin")
    c = c.replace("isPremium", "user has premium subscription")
    c = c.replace("isLoading", "page is loading")
    c = c.replace("isAuth", "user is authenticated")
    if negated:
        c = "NOT " + c
    # Trim and prefix with "if" if not already there
    if not c.lower().startswith("if "):
        c = "if " + c
    return c[:100]


def _find_nearby_selector(content: str, match_start: int, window: int = 300) -> str | None:
    """Look for a data-testid or id attribute near the given match position."""
    snippet = content[max(0, match_start - window): match_start + window]
    m = _NEARBY_SELECTOR_RE.search(snippet)
    if m:
        val = m.group(1)
        attr = "data-testid" if "data-testid" in snippet[max(0, m.start() - 15): m.start()] else "id"
        return f"[{attr}='{val}']"
    return None


# ---------------------------------------------------------------------------
# Main extractor
# ---------------------------------------------------------------------------

def extract_full_navigation(content: str, rel_path: str) -> list[NavEntry]:
    """Extract all navigation entries from source file content.

    Returns list of NavEntry items, deduplicated, up to 25 entries.
    Priority order: redirect > conditional_route > route > modal > state > tab
    """
    entries: list[NavEntry] = []
    seen: set[tuple] = set()

    def _add(entry: NavEntry) -> None:
        key = (entry.mechanism, entry.to, entry.fn, entry.condition)
        if key not in seen:
            seen.add(key)
            entries.append(entry)

    # --- 1. Ternary conditional navigation (highest priority — most informative) ---
    for m in _TERNARY_RE.finditer(content):
        condition_raw = m.group(1).strip().rstrip("(")
        dest_true = m.group(2)
        dest_false = m.group(3)

        if _is_valid_route(dest_true):
            cond = _clean_condition(condition_raw)
            sel = _find_nearby_selector(content, m.start())
            _add(NavEntry(
                mechanism="conditional_route",
                to=dest_true,
                fn=None,
                condition=cond,
                trigger="on_mount",
                selector_hint=sel,
                dynamic=_is_dynamic(dest_true),
            ))
        if _is_valid_route(dest_false):
            # Negate the condition for the else branch
            neg_cond = _clean_condition("!" + condition_raw.strip().lstrip("("))
            sel = _find_nearby_selector(content, m.start())
            _add(NavEntry(
                mechanism="conditional_route",
                to=dest_false,
                fn=None,
                condition=neg_cond,
                trigger="on_mount",
                selector_hint=sel,
                dynamic=_is_dynamic(dest_false),
            ))

    # --- 2. if-block redirects/navigation ---
    for m in _IF_BLOCK_RE.finditer(content):
        condition_raw = m.group(1)
        dest = m.group(2)
        if _is_valid_route(dest):
            cond = _clean_condition(condition_raw)
            trigger = "on_mount"
            sel = _find_nearby_selector(content, m.start())
            _add(NavEntry(
                mechanism="conditional_route",
                to=dest,
                fn=None,
                condition=cond,
                trigger=trigger,
                selector_hint=sel,
                dynamic=_is_dynamic(dest),
            ))

    # --- 3. Callback-context navigation (success/error paths) ---
    for m in _CALLBACK_RE.finditer(content):
        ctx_keyword = m.group(1).lower()
        dest = m.group(2)
        if _is_valid_route(dest):
            if "success" in ctx_keyword or "then" in ctx_keyword:
                trigger = "on_success"
                condition = "on success"
            elif "error" in ctx_keyword or "catch" in ctx_keyword:
                trigger = "on_error"
                condition = "on error"
            elif "submit" in ctx_keyword:
                trigger = "on_submit"
                condition = "after form submission"
            else:
                trigger = "on_success"
                condition = None

            sel = _find_nearby_selector(content, m.start())
            _add(NavEntry(
                mechanism="conditional_route",
                to=dest,
                fn=None,
                condition=condition,
                trigger=trigger,
                selector_hint=sel,
                dynamic=_is_dynamic(dest),
            ))

    # --- 4. Form submit navigation ---
    for m in _SUBMIT_NAV_RE.finditer(content):
        dest = m.group(1)
        if _is_valid_route(dest):
            sel = _find_nearby_selector(content, m.start())
            _add(NavEntry(
                mechanism="conditional_route",
                to=dest,
                fn=None,
                condition="on form submit success",
                trigger="on_submit",
                selector_hint=sel,
                dynamic=_is_dynamic(dest),
            ))

    # --- 5. Next.js middleware redirects ---
    for m in _NEXT_MIDDLEWARE_RE.finditer(content):
        dest = m.group(1)
        if _is_valid_route(dest):
            _add(NavEntry(
                mechanism="redirect",
                to=dest,
                fn=None,
                condition="if user is not authenticated",
                trigger="always",
                selector_hint=None,
                dynamic=_is_dynamic(dest),
            ))

    # --- 6. Next.js server-side redirect() ---
    for m in _NEXTJS_REDIRECT_RE.finditer(content):
        dest = m.group(1)
        if _is_valid_route(dest):
            _add(NavEntry(
                mechanism="redirect",
                to=dest,
                fn=None,
                condition=None,
                trigger="always",
                selector_hint=None,
                dynamic=_is_dynamic(dest),
            ))

    # --- 7. Direct router.push/replace ---
    for m in _ROUTER_PUSH_RE.finditer(content):
        dest = m.group(2)
        if _is_valid_route(dest):
            sel = _find_nearby_selector(content, m.start())
            _add(NavEntry(
                mechanism="route",
                to=dest,
                fn=None,
                condition=None,
                trigger="on_click",
                selector_hint=sel,
                dynamic=_is_dynamic(dest),
            ))

    # --- 8. useNavigate() navigate() calls ---
    for m in _NAVIGATE_RE.finditer(content):
        dest = m.group(1)
        if _is_valid_route(dest):
            sel = _find_nearby_selector(content, m.start())
            _add(NavEntry(
                mechanism="route",
                to=dest,
                fn=None,
                condition=None,
                trigger="on_click",
                selector_hint=sel,
                dynamic=_is_dynamic(dest),
            ))

    # --- 9. Vue Router ---
    for m in _VUE_ROUTER_RE.finditer(content):
        dest = m.group(1) or m.group(2)
        if dest and _is_valid_route(dest):
            _add(NavEntry(
                mechanism="route",
                to=dest,
                fn=None,
                condition=None,
                trigger="on_click",
                selector_hint=None,
                dynamic=_is_dynamic(dest),
            ))

    # --- 10. Angular Router ---
    for m in _ANGULAR_NAVIGATE_RE.finditer(content):
        dest = m.group(1) or m.group(2)
        if dest and _is_valid_route(dest):
            _add(NavEntry(
                mechanism="route",
                to=dest,
                fn=None,
                condition=None,
                trigger="on_click",
                selector_hint=None,
                dynamic=_is_dynamic(dest),
            ))

    # --- 11. NavLink to= ---
    for m in _NAVLINK_RE.finditer(content):
        dest = m.group(1)
        if _is_valid_route(dest):
            _add(NavEntry(
                mechanism="route",
                to=dest,
                fn=None,
                condition=None,
                trigger="on_click",
                selector_hint=None,
                dynamic=_is_dynamic(dest),
            ))

    # --- 12. Link href= / Link to= ---
    for pat in (_LINK_HREF_RE, _LINK_TO_RE):
        for m in pat.finditer(content):
            dest = m.group(1)
            if _is_valid_route(dest):
                _add(NavEntry(
                    mechanism="route",
                    to=dest,
                    fn=None,
                    condition=None,
                    trigger="on_click",
                    selector_hint=None,
                    dynamic=_is_dynamic(dest),
                ))

    # --- 13. React Router <Route path="..."> definitions ---
    for m in _ROUTE_DEF_RE.finditer(content):
        dest = m.group(1)
        if _is_valid_route(dest):
            _add(NavEntry(
                mechanism="route",
                to=dest,
                fn=None,
                condition=None,
                trigger="on_mount",
                selector_hint=None,
                dynamic=_is_dynamic(dest),
            ))

    # --- 14. Modal dispatch ---
    for m in _MODAL_DISPATCH_RE.finditer(content):
        fn = m.group(1)
        key = ("modal", None, fn, None)
        if key not in seen:
            seen.add(key)
            sel = _find_nearby_selector(content, m.start())
            entries.append(NavEntry(
                mechanism="modal",
                to=None,
                fn=fn,
                condition=None,
                trigger="on_click",
                selector_hint=sel,
                dynamic=False,
            ))

    # --- 15. State-driven overlays ---
    for m in _STATE_RE.finditer(content):
        fn = "setShow" + m.group(1)
        key = ("state", None, fn, None)
        if key not in seen:
            seen.add(key)
            sel = _find_nearby_selector(content, m.start())
            entries.append(NavEntry(
                mechanism="state",
                to=None,
                fn=fn,
                condition=None,
                trigger="on_click",
                selector_hint=sel,
                dynamic=False,
            ))

    # --- 16. External tab navigation ---
    for m in _WINDOW_OPEN_RE.finditer(content):
        url = m.group(1)
        key = ("tab", url, None, None)
        if key not in seen:
            seen.add(key)
            entries.append(NavEntry(
                mechanism="tab",
                to=url,
                fn=None,
                condition=None,
                trigger="on_click",
                selector_hint=None,
                dynamic=False,
            ))

    # Remove redundant unconditional route entries when a conditional version exists for same dest
    conditional_dests = {e.to for e in entries if e.mechanism == "conditional_route" and e.to}
    entries = [
        e for e in entries
        if not (e.mechanism == "route" and e.to in conditional_dests)
    ]

    # Prioritize and cap at 25 entries
    # Order: redirect > conditional_route > route > modal > state > tab
    priority = {"redirect": 0, "conditional_route": 1, "route": 2, "modal": 3, "state": 4, "tab": 5, "outlet": 6}
    entries.sort(key=lambda e: priority.get(e.mechanism, 99))
    return entries[:25]


def entry_to_dict(entry: NavEntry) -> dict:
    """Convert NavEntry to a plain dict for JSON serialization."""
    return {
        "mechanism": entry.mechanism,
        "to": entry.to,
        "fn": entry.fn,
        "condition": entry.condition,
        "trigger": entry.trigger,
        "selector_hint": entry.selector_hint,
        "dynamic": entry.dynamic,
    }
