#!/usr/bin/env python3
"""
#exonware/xwnode/src/exonware/xwnode/common/cow/persistent_node.py
Persistent Node - COW Wrapper for Any XWNode Strategy
Wraps any node strategy (HASH_MAP, ARRAY_LIST, B_TREE, etc.) with
Copy-on-Write semantics using HAMT for optimal performance.
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.9.0.20
Generation Date: 26-Oct-2025
"""

from __future__ import annotations
from collections.abc import Iterator
from typing import Any
import copy
from .base import ACOWNode
from .hamt_engine import HAMTEngine


class PersistentNode(ACOWNode):
    """
    Persistent (immutable) node wrapping any XWNode strategy.
    Provides Copy-on-Write semantics on top of any underlying strategy
    (trees, matrices, linear, etc.) using HAMT structural sharing.
    Usage:
        # Wrap any strategy for COW
        strategy = HashMapStrategy()
        immutable_node = PersistentNode.from_strategy(strategy)
        # Or from native data
        immutable_node = PersistentNode.from_native({'key': 'value'})
        # All operations return new nodes
        node2 = immutable_node.set('key', 'new_value')
        assert immutable_node != node2
        assert immutable_node.get('key') == 'value'  # Original unchanged
        assert node2.get('key') == 'new_value'  # New node has new value
    Performance:
    - get(): O(log₃₂ n) via HAMT
    - set(): O(log₃₂ n) with 97% structural sharing
    - to_native(): O(n) reconstruction
    - Memory: ~3% overhead per mutation
    """

    def __init__(self, strategy: HAMTEngine):
        """
        Initialize with HAMT strategy.
        Args:
            strategy: HAMTEngine providing COW operations
        """
        super().__init__(strategy)
    @classmethod

    def from_native(cls, data: Any) -> PersistentNode:
        """
        Create persistent node from native Python data.
        Args:
            data: Native Python data (dict, list, value)
        Returns:
            PersistentNode with COW semantics
        Time Complexity: O(n) to flatten data
        """
        # Flatten native data to paths
        paths = cls._flatten_data(data)
        # Create HAMT engine from paths
        engine = HAMTEngine()
        for path, value in paths.items():
            engine = engine.set_value(path, value)
        return cls(engine)
    @classmethod

    def from_strategy(cls, wrapped_strategy: Any) -> PersistentNode:
        """
        Wrap an existing XWNode strategy with COW.
        Args:
            wrapped_strategy: Any node strategy instance
        Returns:
            PersistentNode wrapping the strategy
        Time Complexity: O(n) to extract and flatten data
        """
        # Convert strategy to native, then create persistent node
        try:
            native_data = wrapped_strategy.to_native()
        except AttributeError:
            # Strategy doesn't have to_native, try direct access
            native_data = wrapped_strategy._data if hasattr(wrapped_strategy, '_data') else {}
        return cls.from_native(native_data)
    @staticmethod

    def _flatten_data(data: Any, prefix: str = '') -> dict[str, Any]:
        """
        Flatten nested data to path -> value mappings.
        Args:
            data: Native Python data
            prefix: Path prefix for recursion
        Returns:
            Dictionary of path -> value mappings
        Time Complexity: O(n) where n is number of values
        """
        paths = {}
        if isinstance(data, dict):
            for key, value in data.items():
                new_prefix = f"{prefix}.{key}" if prefix else key
                if isinstance(value, (dict, list)):
                    # Preserve empty containers (otherwise keys disappear on roundtrip)
                    if len(value) == 0:
                        paths[new_prefix] = value
                    else:
                        paths.update(PersistentNode._flatten_data(value, new_prefix))
                else:
                    paths[new_prefix] = value
        elif isinstance(data, list):
            for i, value in enumerate(data):
                new_prefix = f"{prefix}.{i}" if prefix else str(i)
                if isinstance(value, (dict, list)):
                    # Preserve empty containers (otherwise indices disappear on roundtrip)
                    if len(value) == 0:
                        paths[new_prefix] = value
                    else:
                        paths.update(PersistentNode._flatten_data(value, new_prefix))
                else:
                    paths[new_prefix] = value
        else:
            # Scalar value
            paths[prefix if prefix else ''] = data
        return paths

    def copy(self) -> PersistentNode:
        """
        Create a copy (shares structure due to immutability).
        Returns:
            Self (immutable, so copy is same as original)
        Time Complexity: O(1) - just return self
        """
        # Immutable nodes don't need copying - they never change
        return self

    def __eq__(self, other: Any) -> bool:
        """
        Check equality by version.
        Args:
            other: Other node to compare
        Returns:
            True if same version (same data)
        """
        if not isinstance(other, PersistentNode):
            return False
        return self.get_version() == other.get_version()

    def __hash__(self) -> int:
        """
        Hash based on version.
        Returns:
            Hash value
        """
        return hash(self.get_version())

    def __repr__(self) -> str:
        """String representation."""
        return f"PersistentNode(version={self.get_version()}, paths={len(self)})"
    # ==========================================================================
    # STRATEGY COMPATIBILITY
    # ==========================================================================

    def size(self) -> int:
        """
        Get number of items (for XWNode facade compatibility).
        Returns:
            Number of paths in node
        """
        return len(self)

    def is_empty(self) -> bool:
        """
        Check if node is empty (for XWNode facade compatibility).
        Returns:
            True if no paths
        """
        return len(self) == 0

    def keys(self):
        """Iterate over paths (for XWNode facade compatibility)."""
        return iter(self)

    def values(self):
        """Iterate over values (for XWNode facade compatibility)."""
        paths = self._strategy.get_paths()
        return iter(paths.values())

    def items(self):
        """Iterate over path, value pairs (for XWNode facade compatibility)."""
        paths = self._strategy.get_paths()
        return iter(paths.items())
