#!/usr/bin/env python3
"""
#exonware/xwnode/src/exonware/xwnode/nodes/strategies/suffix_array.py
Suffix Array Node Strategy Implementation
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.9.0.20
Generation Date: 16-Jan-2026
"""

from __future__ import annotations
from collections.abc import AsyncIterator, Iterator
"""
Suffix Array Node Strategy Implementation
This module implements the SUFFIX_ARRAY strategy for efficient substring
searches and string pattern matching with linear time construction.
"""
from typing import Any
import bisect
from .base import ANodeTreeStrategy
from .contracts import NodeType
from ...defs import NodeMode, NodeTrait
from ...errors import XWNodeUnsupportedCapabilityError


class SuffixArrayStrategy(ANodeTreeStrategy):
    """
    Suffix Array node strategy for efficient string operations.
    Provides fast substring searches, pattern matching, and string analysis
    with linear space usage and eff
    # Strategy type classification
    STRATEGY_TYPE = NodeType.TREE
icient query operations.
    """

    def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
        """
        Initialize the Suffix Array strategy.
        Time Complexity: O(1)
        Space Complexity: O(1)
        """
        super().__init__(NodeMode.SUFFIX_ARRAY, traits, **options)
        self.enable_lcp = options.get('enable_lcp', True)  # Longest Common Prefix array
        self.case_sensitive = options.get('case_sensitive', True)
        self.separator = options.get('separator', '$')  # End-of-string marker
        # Core storage
        self._text = ""
        self._suffix_array: list[int] = []
        self._lcp_array: list[int] = []  # Longest Common Prefix
        self._rank: list[int] = []  # Inverse suffix array
        # Key-value mapping for compatibility
        self._key_to_pos: dict[str, list[int]] = {}
        self._values: dict[str, Any] = {}
        self._size = 0
        # Performance optimizations
        self._is_built = False
        self._pattern_cache: dict[str, list[int]] = {}

    def get_supported_traits(self) -> NodeTrait:
        """Get the traits supported by the suffix array strategy."""
        return (NodeTrait.ORDERED | NodeTrait.INDEXED | NodeTrait.STREAMING)

    def _preprocess_text(self, text: str) -> str:
        """Preprocess text for suffix array construction."""
        if not self.case_sensitive:
            text = text.lower()
        # Ensure text ends with separator
        if not text.endswith(self.separator):
            text += self.separator
        return text

    def _build_suffix_array_naive(self) -> None:
        """Build suffix array using naive O(n²log n) algorithm."""
        n = len(self._text)
        suffixes = []
        for i in range(n):
            suffixes.append((self._text[i:], i))
        # Sort suffixes lexicographically
        suffixes.sort()
        self._suffix_array = [suffix[1] for suffix in suffixes]
        self._build_rank_array()
        if self.enable_lcp:
            self._build_lcp_array()

    def _build_suffix_array_optimized(self) -> None:
        """Build suffix array using optimized radix sort approach."""
        # For simplicity, using naive approach - can be optimized with DC3/SA-IS algorithms
        self._build_suffix_array_naive()

    def _build_rank_array(self) -> None:
        """Build rank array (inverse of suffix array)."""
        n = len(self._suffix_array)
        self._rank = [0] * n
        for i in range(n):
            self._rank[self._suffix_array[i]] = i

    def _build_lcp_array(self) -> None:
        """Build Longest Common Prefix array using Kasai's algorithm."""
        n = len(self._text)
        self._lcp_array = [0] * n
        if n == 0:
            return
        k = 0
        for i in range(n):
            if self._rank[i] == n - 1:
                k = 0
                continue
            j = self._suffix_array[self._rank[i] + 1]
            while (i + k < n and j + k < n and 
                   self._text[i + k] == self._text[j + k]):
                k += 1
            self._lcp_array[self._rank[i]] = k
            if k > 0:
                k -= 1

    def _rebuild_if_needed(self) -> None:
        """Rebuild suffix array if text has changed."""
        if not self._is_built and self._text:
            self._build_suffix_array_optimized()
            self._is_built = True
    # ============================================================================
    # CORE OPERATIONS (Key-based interface for compatibility)
    # ============================================================================

    def put(self, key: Any, value: Any = None) -> None:
        """Add string to suffix array."""
        key_str = str(key)
        # If this is the first key or a text replacement
        if not self._text or key_str == "text":
            # Replace entire text
            self._text = self._preprocess_text(str(value) if value else key_str)
            self._is_built = False
            self._pattern_cache.clear()
            self._key_to_pos.clear()
            self._values[key_str] = value
            self._size = 1
        else:
            # Append to text (less efficient, requires rebuild)
            if self._text.endswith(self.separator):
                self._text = self._text[:-1] + str(value) + self.separator
            else:
                self._text += str(value) + self.separator
            self._is_built = False
            self._pattern_cache.clear()
            self._values[key_str] = value
            self._size += 1

    def get(self, key: Any, default: Any = None) -> Any:
        """Get value by key."""
        key_str = str(key)
        if key_str == "text":
            # Return text without separator for user
            text = self._text
            if text.endswith(self.separator):
                text = text[:-1]
            return text
        elif key_str == "suffix_array":
            self._rebuild_if_needed()
            return self._suffix_array.copy()
        elif key_str == "lcp_array":
            self._rebuild_if_needed()
            return self._lcp_array.copy()
        elif key_str in self._values:
            return self._values[key_str]
        return default

    def has(self, key: Any) -> bool:
        """Check if key exists."""
        key_str = str(key)
        return key_str in self._values or key_str in ["text", "suffix_array", "lcp_array"]

    def remove(self, key: Any) -> bool:
        """Remove key (limited support)."""
        key_str = str(key)
        if key_str in self._values:
            del self._values[key_str]
            self._size -= 1
            return True
        return False

    def delete(self, key: Any) -> bool:
        """Remove key (alias for remove)."""
        return self.remove(key)

    def clear(self) -> None:
        """Clear all data."""
        self._text = ""
        self._suffix_array.clear()
        self._lcp_array.clear()
        self._rank.clear()
        self._key_to_pos.clear()
        self._values.clear()
        self._pattern_cache.clear()
        self._size = 0
        self._is_built = False

    def keys(self) -> Iterator[str]:
        """Get all keys."""
        yield "text"
        yield "suffix_array"
        if self.enable_lcp:
            yield "lcp_array"
        for key in self._values.keys():
            yield key

    def values(self) -> Iterator[Any]:
        """Get all values."""
        yield self._text
        self._rebuild_if_needed()
        yield self._suffix_array.copy()
        if self.enable_lcp:
            yield self._lcp_array.copy()
        for value in self._values.values():
            yield value

    def items(self) -> Iterator[tuple[str, Any]]:
        """Get all key-value pairs."""
        yield ("text", self._text)
        self._rebuild_if_needed()
        yield ("suffix_array", self._suffix_array.copy())
        if self.enable_lcp:
            yield ("lcp_array", self._lcp_array.copy())
        for key, value in self._values.items():
            yield (key, value)

    def __len__(self) -> int:
        """Get number of stored items."""
        return self._size

    def to_native(self) -> dict[str, Any]:
        """Convert to native Python dict."""
        result = {"text": self._text}
    # ============================================================================
    # ASYNC API - Lightweight wrappers (NO lock overhead, v0.0.1.28b)
    # ============================================================================

    async def insert_async(self, key: Any, value: Any) -> None:
        """Lightweight async wrapper for insert (no lock overhead)."""
        return self.insert(key, value)

    async def find_async(self, key: Any) -> Any | None:
        """Lightweight async wrapper for find (no lock overhead)."""
        return self.find(key)

    async def delete_async(self, key: Any) -> bool:
        """Lightweight async wrapper for delete (no lock overhead)."""
        return self.delete(key)

    async def size_async(self) -> int:
        """Lightweight async wrapper for size (no lock overhead)."""
        return self.size()

    async def is_empty_async(self) -> bool:
        """Lightweight async wrapper for is_empty (no lock overhead)."""
        return self.is_empty()

    async def to_native_async(self) -> Any:
        """Lightweight async wrapper for to_native (no lock overhead)."""
        return self.to_native()

    async def keys_async(self) -> AsyncIterator[Any]:
        """Lightweight async wrapper for keys (no lock overhead)."""
        for key in self.keys():
            yield key

    async def values_async(self) -> AsyncIterator[Any]:
        """Lightweight async wrapper for values (no lock overhead)."""
        for value in self.values():
            yield value

    async def items_async(self) -> AsyncIterator[tuple[Any, Any]]:
        """Lightweight async wrapper for items (no lock overhead)."""
        for item in self.items():
            yield item
    @property

    def is_list(self) -> bool:
        """This can behave like a list for suffix access."""
        return True
    @property

    def is_dict(self) -> bool:
        """This behaves like a dict."""
        return True
    # ============================================================================
    # SUFFIX ARRAY SPECIFIC OPERATIONS
    # ============================================================================

    def set_text(self, text: str) -> None:
        """Set the text for suffix array operations."""
        self._text = self._preprocess_text(text)
        self._is_built = False
        self._pattern_cache.clear()
        self._size = 1

    def search_pattern(self, pattern: str) -> list[int]:
        """Search for pattern occurrences using binary search."""
        if not pattern:
            return []
        # Check cache first
        if pattern in self._pattern_cache:
            return self._pattern_cache[pattern]
        self._rebuild_if_needed()
        if not self._suffix_array:
            return []
        if not self.case_sensitive:
            pattern = pattern.lower()
        # Binary search for leftmost occurrence
        left = self._binary_search_left(pattern)
        if left == -1:
            self._pattern_cache[pattern] = []
            return []
        # Binary search for rightmost occurrence
        right = self._binary_search_right(pattern)
        # Extract all matching positions
        positions = []
        for i in range(left, right + 1):
            pos = self._suffix_array[i]
            positions.append(pos)
        positions.sort()
        self._pattern_cache[pattern] = positions
        return positions

    def _binary_search_left(self, pattern: str) -> int:
        """Find leftmost occurrence of pattern."""
        left, right = 0, len(self._suffix_array) - 1
        result = -1
        while left <= right:
            mid = (left + right) // 2
            suffix_pos = self._suffix_array[mid]
            suffix = self._text[suffix_pos:]
            if suffix.startswith(pattern):
                result = mid
                right = mid - 1  # Continue searching left
            elif suffix < pattern:
                left = mid + 1
            else:
                right = mid - 1
        return result

    def _binary_search_right(self, pattern: str) -> int:
        """Find rightmost occurrence of pattern."""
        left, right = 0, len(self._suffix_array) - 1
        result = -1
        while left <= right:
            mid = (left + right) // 2
            suffix_pos = self._suffix_array[mid]
            suffix = self._text[suffix_pos:]
            if suffix.startswith(pattern):
                result = mid
                left = mid + 1  # Continue searching right
            elif suffix < pattern:
                left = mid + 1
            else:
                right = mid - 1
        return result

    def count_occurrences(self, pattern: str) -> int:
        """Count occurrences of pattern."""
        return len(self.search_pattern(pattern))

    def find_longest_common_substring(self, other_text: str) -> tuple[str, int, int]:
        """Find longest common substring with another text."""
        if not self._text or not other_text:
            return "", 0, 0
        # Create combined text with separator
        combined = self._text + "#" + other_text + self.separator
        original_text = self._text
        # Temporarily set combined text
        self.set_text(combined)
        self._rebuild_if_needed()
        # Find longest common substring using LCP array
        max_lcp = 0
        max_pos = 0
        text1_len = len(original_text)
        for i in range(len(self._lcp_array) - 1):
            pos1 = self._suffix_array[i]
            pos2 = self._suffix_array[i + 1]
            # Check if suffixes are from different texts
            if ((pos1 < text1_len) != (pos2 < text1_len)) and self._lcp_array[i] > max_lcp:
                max_lcp = self._lcp_array[i]
                max_pos = min(pos1, pos2)
        # Restore original text
        self.set_text(original_text)
        if max_lcp > 0:
            lcs = combined[max_pos:max_pos + max_lcp]
            return lcs, max_pos, max_lcp
        return "", 0, 0

    def get_suffix(self, index: int) -> str:
        """Get suffix starting at given index."""
        if 0 <= index < len(self._text):
            return self._text[index:]
        return ""

    def get_sorted_suffixes(self) -> list[str]:
        """Get all suffixes in sorted order."""
        self._rebuild_if_needed()
        suffixes = []
        for pos in self._suffix_array:
            suffixes.append(self._text[pos:])
        return suffixes

    def find_repeated_substrings(self, min_length: int = 2) -> list[tuple[str, int, list[int]]]:
        """Find repeated substrings using LCP array."""
        self._rebuild_if_needed()
        if not self.enable_lcp:
            return []
        repeated = []
        for i in range(len(self._lcp_array)):
            lcp_len = self._lcp_array[i]
            if lcp_len >= min_length:
                pos1 = self._suffix_array[i]
                pos2 = self._suffix_array[i + 1]
                substring = self._text[pos1:pos1 + lcp_len]
                # Find all occurrences of this substring
                positions = self.search_pattern(substring)
                if len(positions) > 1:
                    repeated.append((substring, lcp_len, positions))
        # Remove duplicates and sort by length
        unique_repeated = {}
        for substr, length, positions in repeated:
            if substr not in unique_repeated or len(positions) > len(unique_repeated[substr][1]):
                unique_repeated[substr] = (length, positions)
        result = [(substr, data[0], data[1]) for substr, data in unique_repeated.items()]
        result.sort(key=lambda x: x[1], reverse=True)
        return result

    def get_statistics(self) -> dict[str, Any]:
        """Get comprehensive suffix array statistics."""
        self._rebuild_if_needed()
        if not self._text:
            return {'text_length': 0, 'unique_characters': 0, 'suffixes': 0}
        unique_chars = len(set(self._text))
        avg_lcp = sum(self._lcp_array) / len(self._lcp_array) if self._lcp_array else 0
        max_lcp = max(self._lcp_array) if self._lcp_array else 0
        return {
            'text_length': len(self._text),
            'unique_characters': unique_chars,
            'suffixes': len(self._suffix_array),
            'avg_lcp': avg_lcp,
            'max_lcp': max_lcp,
            'case_sensitive': self.case_sensitive,
            'pattern_cache_size': len(self._pattern_cache),
            'memory_usage': len(self._text) + len(self._suffix_array) * 4 + len(self._lcp_array) * 4
        }
    # ============================================================================
    # PERFORMANCE CHARACTERISTICS
    # ============================================================================
    @property

    def backend_info(self) -> dict[str, Any]:
        """Get backend implementation info."""
        return {
            'strategy': 'SUFFIX_ARRAY',
            'backend': 'Suffix array with LCP array and binary search',
            'enable_lcp': self.enable_lcp,
            'case_sensitive': self.case_sensitive,
            'separator': self.separator,
            'complexity': {
                'construction': 'O(n log n)',  # Can be optimized to O(n)
                'pattern_search': 'O(m log n + occ)',  # m = pattern length, occ = occurrences
                'space': 'O(n)',
                'lcp_construction': 'O(n)',
                'substring_queries': 'O(log n + occ)'
            }
        }
    @property

    def metrics(self) -> dict[str, Any]:
        """Get performance metrics."""
        stats = self.get_statistics()
        return {
            'text_length': stats['text_length'],
            'suffixes': stats['suffixes'],
            'unique_chars': stats['unique_characters'],
            'avg_lcp': f"{stats['avg_lcp']:.2f}",
            'max_lcp': stats['max_lcp'],
            'cache_entries': stats['pattern_cache_size'],
            'memory_usage': f"{stats['memory_usage']} bytes"
        }

    def add_edge(self, from_node: Any, to_node: Any, weight: float = 1.0) -> None:
        """Not supported - this is a tree/map strategy, not a graph."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support graph edges")

    def remove_edge(self, from_node: Any, to_node: Any) -> bool:
        """Not supported - this is a tree/map strategy, not a graph."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support graph edges")

    def has_edge(self, from_node: Any, to_node: Any) -> bool:
        """Not supported - this is a tree/map strategy, not a graph."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support graph edges")

    def find_path(self, start: Any, end: Any) -> list[Any]:
        """Not supported - this is a tree/map strategy, not a graph."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support graph paths")

    def get_neighbors(self, node: Any) -> list[Any]:
        """Not supported - this is a tree/map strategy, not a graph."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support graph neighbors")

    def get_edge_weight(self, from_node: Any, to_node: Any) -> float:
        """Not supported - this is a tree/map strategy, not a graph."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support graph edges")

    def traverse(self, order: str = 'inorder') -> list[Any]:
        """Traverse - returns all key-value pairs."""
        return list(self.items())

    def get_min(self) -> Any:
        """Get minimum key."""
        keys = list(self.keys())
        if not keys:
            return None
        return min(keys)

    def get_max(self) -> Any:
        """Get maximum key."""
        keys = list(self.keys())
        if not keys:
            return None
        return max(keys)

    def as_union_find(self):
        """Not supported."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support union-find view")

    def as_neural_graph(self):
        """Not supported."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support neural graph view")

    def as_flow_network(self):
        """Not supported."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support flow network view")

    def as_trie(self):
        """Not supported."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support trie view")

    def as_heap(self):
        """Not supported."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support heap view")

    def as_skip_list(self):
        """Not supported."""
        raise XWNodeUnsupportedCapabilityError(f"{self.__class__.__name__} does not support skip list view")
