#!/usr/bin/env python3
"""
PAGE_RANK Executor
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.0.1.8
"""

from typing import Any, Optional
from ..base import AUniversalOperationExecutor
from ....contracts import QueryAction, ExecutionContext, ExecutionResult
from ....defs import OperationType
from ..utils import extract_items, extract_field_value


class PageRankExecutor(AUniversalOperationExecutor):
    """PAGE_RANK - Iterative PageRank over adjacency links."""
    OPERATION_NAME = "PAGE_RANK"
    OPERATION_TYPE = OperationType.GRAPH
    SUPPORTED_NODE_TYPES = []

    def _do_execute(self, action: QueryAction, context: ExecutionContext) -> ExecutionResult:
        params = action.params
        result_data = self._execute_page_rank(context.node, params, context)
        return ExecutionResult(
            success=True,
            data=result_data,
            action_type=self.OPERATION_NAME,
            metadata={"operation": self.OPERATION_NAME},
        )

    def _execute_page_rank(self, node: Any, params: dict, context: ExecutionContext) -> dict:
        items = extract_items(node)
        damping = float(params.get("damping", 0.85))
        iterations = int(params.get("iterations", 20))
        edge_field = params.get("edge_field", "edges")
        as_field = params.get("as_field", "page_rank")
        id_field = params.get("id_field", "id")

        node_ids = [extract_field_value(item, id_field) for item in items]
        node_ids = [node_id for node_id in node_ids if node_id is not None]
        node_count = len(node_ids)
        if node_count == 0:
            return {"items": [], "count": 0, "error": "No nodes found"}

        adjacency: dict[Any, list[Any]] = {}
        for item in items:
            source_id = extract_field_value(item, id_field)
            neighbors = extract_field_value(item, edge_field)
            if source_id is None:
                continue
            if isinstance(neighbors, list):
                adjacency[source_id] = [n for n in neighbors if n in node_ids]
            else:
                adjacency[source_id] = []

        ranks = {node_id: 1.0 / node_count for node_id in node_ids}
        base = (1.0 - damping) / node_count

        for _ in range(iterations):
            new_ranks = {node_id: base for node_id in node_ids}
            for source_id in node_ids:
                outgoing = adjacency.get(source_id, [])
                if outgoing:
                    share = ranks[source_id] / len(outgoing)
                    for target_id in outgoing:
                        new_ranks[target_id] += damping * share
                else:
                    share = ranks[source_id] / node_count
                    for target_id in node_ids:
                        new_ranks[target_id] += damping * share
            ranks = new_ranks

        output = []
        for item in items:
            record = dict(item) if isinstance(item, dict) else {"value": item}
            record[as_field] = ranks.get(extract_field_value(item, id_field), 0.0)
            output.append(record)

        return {
            "items": output,
            "count": len(output),
            "iterations": iterations,
            "damping": damping,
            "as_field": as_field,
        }
