""" Defines procedural entries which are generated using entry graphs.
"""
from __future__ import annotations

from abc import ABC
from typing import ClassVar, final, Optional

import chemrecon.entrygraph.scoring as scoring

import chemrecon.entrygraph.explore as explore
from chemrecon.schema.db_object import Entry, ProceduralRelation
from chemrecon.schema.db_object import col_score
from chemrecon.entrygraph.explorationprotocol import ExplorationProtocol
from chemrecon.entrygraph.entrygraph import EntryGraph


class ProceduralRelationEG[T1: Entry, T2: Entry](ProceduralRelation[T1, T2], ABC):
    """ A procedural relation which is calculated based on an entrygraph of other relations
        in the database.
        Requires entrygraph to only accept entries of type T1 as initial entries, and the
        scorer to score entries of type T2.
    """
    # Attribute
    score: float  #: The score of the target entry in the entry graph.

    # Defines the parameters from which the EG is generated
    protocol: ClassVar[ExplorationProtocol]
    entrygraph_scorer: ClassVar[scoring.Scorer]

    # Necessary
    symmetric = False
    _attribute_columns = [
        col_score
    ]
    _index = []


    def __init__(
        self,
        recon_id_1: Optional[int] = None,
        recon_id_2: Optional[int] = None,
        score: float = 0.0
    ):
        self.score = score
        super().__init__(recon_id_1 = recon_id_1, recon_id_2 = recon_id_2)

    @final
    @classmethod
    def generate(
        cls,
        take_entry: T1,
    ) -> list[tuple[ProceduralRelation[T1, T2], T2]]:
        """ Generation of procedural relations relies on generating the associated entrygraph.
        """
        eg = EntryGraph(
            initial_entries = {take_entry}
        )
        explore.explore(eg, steps = 4, protocol = cls.protocol)
        score = cls.entrygraph_scorer(eg)
        return [
            (cls(recon_id_1 = None, recon_id_2 = None, score = score), score_entry)
            for score_entry, score in score.items()
        ]

    def _vis_str(self) -> str:
        return f'score: {self.score}'
