#
# Copyright 2021-2025 WhiteMech
#
# ------------------------------
#
# This file is part of pddl.
#
# Use of this source code is governed by an MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.
#

"""This module contains the definition of the PDDL action class."""

from typing import Optional, Sequence

from pddl.custom_types import namelike, parse_name
from pddl.helpers.base import _typed_parameters, ensure_sequence
from pddl.logic import Variable
from pddl.logic.base import Formula
from pddl.logic.terms import Term


class Action:
    """A class for the PDDL Action."""

    def __init__(
        self,
        name: namelike,
        parameters: Sequence[Term],
        precondition: Optional[Formula] = None,
        effect: Optional[Formula] = None,
    ):
        """
        Initialize the action.

        :param name: the action name.
        :param parameters: the action parameters.
        :param precondition: the action precondition.
        :param effect: the action effect.
        """
        self._name: str = parse_name(name)
        self._parameters: Sequence[Variable] = ensure_sequence(parameters)
        self._precondition = precondition
        self._effect = effect

    @property
    def name(self) -> str:
        """Get the name."""
        return self._name

    @property
    def parameters(self) -> Sequence[Variable]:
        """Get the parameters."""
        return self._parameters

    @property
    def terms(self) -> Sequence[Term]:
        """Get the terms."""
        return self.parameters

    @property
    def precondition(self) -> Optional[Formula]:
        """Get the precondition."""
        return self._precondition

    @property
    def effect(self) -> Optional[Formula]:
        """Get the effect."""
        return self._effect

    def instantiate(self, mapping: Sequence[Term]) -> "Action":
        """
        Instantiate the action by replacing its parameters according to the given mapping.

        :param mapping: a list of terms to replace the parameters.
        :return: the instantiated action.
        """
        if len(mapping) != len(self.parameters):
            raise ValueError(
                f"Mapping length {len(mapping)} does not match number of parameters {len(self.parameters)}."
            )

        instantiation = {param: const for param, const in zip(self.parameters, mapping)}

        return Action(
            name=self.name,
            parameters=mapping,
            precondition=(
                self.precondition.instantiate(instantiation)
                if self.precondition
                else None
            ),
            effect=self.effect.instantiate(instantiation) if self.effect else None,
        )

    def __str__(self):
        """Get the string."""
        operator_str = "(:action {0}\n".format(self.name)
        operator_str += f"    :parameters ({_typed_parameters(self.parameters)})\n"
        if self.precondition is not None:
            operator_str += f"    :precondition {str(self.precondition)}\n"
        if self.effect is not None:
            operator_str += f"    :effect {str(self.effect)}\n"
        operator_str += ")"
        return operator_str

    def __eq__(self, other):
        """Check equality between two Actions."""
        return (
            isinstance(other, Action)
            and self.name == other.name
            and self.parameters == other.parameters
            and self.precondition == other.precondition
            and self.effect == other.effect
        )

    def __hash__(self):
        """Get the hash."""
        return hash((self.name, self.parameters, self.precondition, self.effect))

    def __repr__(self) -> str:
        """Get an unambiguous string representation."""
        return (
            f"{type(self).__name__}({self.name}, parameters={', '.join(map(str, self.parameters))}, "
            f"precondition={self.precondition}, effect={self.effect})"
        )
