text: import typing
  from typing import Optional, Dict, List, Set
  
  import attr
  import cattr
  from ..jsonapi.relationship import Relationship
  from ..jsonapi.resource import Resource
  from ..jsonapi.document import Document
  from .....rescue import Rescue as InternalRescue
  from .....mark_for_deletion import MarkForDeletion
  from src.packages.fuelrats_api.v3.converters import to_datetime
  from .quotation import Quotation
  from .....utils import Platforms
  import pendulum
  
  
  @attr.dataclass
  class RescueAttributes:
      client: str = attr.ib(validator=attr.validators.instance_of(str))
      clientNick: Optional[str] = attr.ib(
          validator=attr.validators.optional(attr.validators.instance_of(str))
      )
      clientLanguage: Optional[str] = attr.ib(
          validator=attr.validators.optional(attr.validators.instance_of(str))
      )
      codeRed: bool = attr.ib(validator=attr.validators.instance_of(bool))
      data: Dict = attr.ib(validator=attr.validators.instance_of(dict))  # ???
      notes: Optional[str] = attr.ib(validator=attr.validators.optional(
          attr.validators.instance_of(str))
      )
      platform: Optional[Platforms] = attr.ib(
          validator=attr.validators.optional(attr.validators.instance_of(Platforms))
      )
      system: Optional[str] = attr.ib(
          validator=attr.validators.optional(attr.validators.instance_of(str))
      )
      title: Optional[str] = attr.ib(
          validator=attr.validators.optional(attr.validators.instance_of(str))
      )
      unidentifiedRats: List[str] = attr.ib(
          validator=attr.validators.deep_iterable(
              member_validator=attr.validators.instance_of(str),
              iterable_validator=attr.validators.instance_of(list),
          )
      )
      createdAt: pendulum.DateTime = attr.ib(
          validator=attr.validators.instance_of(pendulum.DateTime), converter=to_datetime
      )
      updatedAt: pendulum.DateTime = attr.ib(
          validator=attr.validators.instance_of(pendulum.DateTime), converter=to_datetime
      )
      status: str = attr.ib(validator=attr.validators.instance_of(str))
      outcome: Optional[str] = attr.ib(
          validator=attr.validators.optional(attr.validators.instance_of(str))
      )
      quotes: List[Quotation] = attr.ib(
          validator=attr.validators.deep_iterable(
              member_validator=attr.validators.instance_of(Quotation),
              iterable_validator=attr.validators.instance_of(list),
          )
      )
      commandIdentifier: Optional[int] = attr.ib(
          validator=attr.validators.optional(attr.validators.instance_of(int)),
          converter=attr.converters.optional(int),
      )
      """ Board index. """
  
      @classmethod
      def from_dict(cls, data: Dict) -> "RescueAttributes":
          data = data.copy()  # shallow copy because callers might get confused if we mutate theirs...
          data["quotes"] = [Quotation.from_dict(obj) for obj in data["quotes"]]
          return cls(**data)
  
      @classmethod
      def from_internal(cls, data: InternalRescue) -> "RescueAttributes":
          return cls(
              client=data.client,
              clientNick=data.irc_nickname,
              clientLanguage=data.lang_id,
              codeRed=data.code_red,
              data={},
              # MFD reason translates into the notes field
              notes=data.marked_for_deletion.reason if data.marked_for_deletion.marked else "",
              platform=data.platform,
              system=data.system,
              title=data.title,
              unidentifiedRats=[obj.name for obj in data.unidentified_rats.values()],
              createdAt=data.created_at,
              updatedAt=data.updated_at,
              status=data.status.name.lower(),
              # MFD translates to `purge` outcome, all other outcomes are not set by Mecha.
              outcome="purge" if data.marked_for_deletion.marked else None,
              quotes=[Quotation.from_internal(obj) for obj in data.quotes],
              # I did have to ask what this translated to...
              commandIdentifier=data.board_index,
          )
  
  
  @attr.dataclass
  class RescueRelationships:
      rats: Relationship
      firstLimpet: Relationship
      epics: Relationship
  
  
  @attr.dataclass
  class Rescue(Resource):
      type: str = "rescues"
      attributes: Optional[RescueAttributes] = None
      relationships: Optional[RescueRelationships] = None
  
      def into_internal(self) -> InternalRescue:
          return InternalRescue(
              uuid=self.id,
              client=self.attributes.client,
              system=self.attributes.system,
              irc_nickname=self.attributes.clientNick,
              created_at=self.attributes.createdAt,
              updated_at=self.attributes.updatedAt,
              unidentified_rats=self.attributes.unidentifiedRats,
              quotes=[quote.into_internal() for quote in self.attributes.quotes],
              title=self.attributes.title,
              first_limpet=self.relationships.firstLimpet.data.id
              if self.relationships and self.relationships.firstLimpet.data
              else None,
              board_index=self.attributes.commandIdentifier,
              lang_id=self.attributes.clientLanguage,
              # TODO rest of attributes
              mark_for_deletion=MarkForDeletion(
                  marked=self.attributes.outcome == "purge",
                  reason=self.attributes.notes if self.attributes.notes else None,
              ),
              platform=self.attributes.platform,
          )
  
      @classmethod
      def from_internal(cls, data: InternalRescue) -> "Rescue":
          return Rescue(id=data.api_id, attributes=RescueAttributes.from_internal(data))
  
      def to_delta(self, changes: Set[str]) -> Dict:
          """
          Converts this API rescue object to a dictionary blob delta based on changed fields from
          an internal rescue object. `changes` should contain only attribute names on the **internal**
          rescue object.
  
  
  
          Args:
              changes: set of changed InternalRescue attributes
  
          Returns:
              json blob of API Rescue
          """
          field_map = {
              "client": "client",
              "system": "system",
              "irc_nick": "clientNick",
              "unidentified_rats": "unidentifiedRats",
              "quotes": "quotes",
              "title": "title",
              "board_index": "commandIdentifier",
              "lang_id": "clientLanguage",
              "status": "status",
              "code_red": "codeRed",
              "platform": "platform",
          }
          # translate internal datamodel names to the APIs datamodel names
          keep = {field_map[field] for field in changes if field != "mark_for_deletion"}
  
          if "mark_for_deletion" in changes:
              keep |= {"outcome"}
              self.attributes.outcome = "purge"
          # serialize API rescue object
          data = attr.asdict(self, recurse=True)
          # figure out which keys we need to keep (only send the ones modified internally)
          kept_attribs = {key: value for key, value in data["attributes"].items() if key in keep}
          # and patch the object.
          data["attributes"] = kept_attribs
          return data
  
  
  @attr.dataclass
  class RescueDocument(Document):
      data: Rescue
  
  
  cattr.register_unstructure_hook(Rescue, lambda rescue: rescue.to_delta())
  