"""Module template was autogenerated from sharepoint list schema."""

import re
from dataclasses import dataclass
from datetime import date, datetime
from decimal import Decimal, DecimalException
from enum import Enum
from typing import List, Optional

from aind_data_schema.core.procedures import (
    Anaesthetic,
    BrainInjection,
    CoordinateReferenceLocation,
    Craniotomy,
    CraniotomyType,
    FiberImplant,
    Headframe,
    IontophoresisInjection,
    NanojectInjection,
    OphysProbe,
    Side,
    Surgery,
    ViralMaterial,
)
from aind_data_schema.core.subject import Sex
from aind_data_schema.models.devices import FiberProbe

from aind_metadata_service.sharepoint.nsb2019.models import NSBList
from aind_metadata_service.sharepoint.nsb2019.models import (
    Procedure as NSBProcedure,
)


@dataclass
class HeadPostInfo:
    """Container for head post information"""

    headframe_type: Optional[str] = None
    headframe_part_number: Optional[str] = None
    well_type: Optional[str] = None
    well_part_number: Optional[str] = None


class InjectionType(Enum):
    """Enum class for Injection Types"""

    NANOJECT = "Nanoject"
    IONTOPHORESIS = "Iontophoresis"


class InjectionRound(Enum):
    """Enum class for Injection Rounds"""

    FIRST = "First"
    SECOND = "Second"


class MappedNSBList:
    """Mapped Fields in Sharepoint list"""

    AP_REGEX = re.compile(r"^ *([-+]?\d*\.?\d+) *(?:mm)? *$")
    DV_REGEX = re.compile(r"^ *([-+]?\d*\.?\d+) *(?:mm)? *$")
    ISO_DUR_REGEX1 = re.compile(r"^ *(\d*\.?\d+) *(?:hour|hours)* *$")
    ISO_DUR_REGEX2 = re.compile(r"^(\d+):(\d+)$")
    ML_REGEX = re.compile(r"^ *([-+]?\d*\.?\d+) *(?:mm)? *$")
    ALT_TIME_REGEX = re.compile(
        r"^ *(7)(?:.0|/7)? *(?:s|sec|secs|second|seconds)? *$"
    )
    INJ_ANGLE_REGEX = re.compile(
        r"^ *([-+]?\d*\.?\d+) *(?:deg|degree|degrees)* *$"
    )
    CURRENT_REGEX = re.compile(r"^ *([-+]?\d*\.?\d+) *(?:ua|uA|uAmp)* *$")
    LENGTH_OF_TIME_REGEX = re.compile(
        r"^ *(\d*\.?\d+) *(?:m|min|mins|minute|minutes)+ *$"
    )
    VOLUME_REGEX = re.compile(r"^ *(\d*\.?\d+) *(?:nl|nL)+ *$")

    def __init__(self, nsb: NSBList):
        """Class constructor"""
        self._nsb = nsb

    @staticmethod
    def _parse_basic_decimal_str(value: Optional[str]) -> Optional[Decimal]:
        """Parse string representation of Decimal such as '0.25'."""
        try:
            return None if value is None else Decimal(value)
        except (ValueError, DecimalException):
            return None

    @staticmethod
    def _parse_basic_float_str(float_str: Optional[str]) -> Optional[float]:
        """Parse string representation of float such as '0.25'."""
        try:
            return None if float_str is None else float(float_str)
        except ValueError:
            return None

    def _parse_ap_str(self, ap_str: Optional[str]) -> Optional[Decimal]:
        """Parse AP String."""
        if ap_str is not None:
            parsed_string = re.search(self.AP_REGEX, ap_str)
            if parsed_string is not None:
                return self._parse_basic_decimal_str(parsed_string.group(1))
            else:
                return None
        else:
            return None

    def _parse_dv_str(self, dv_str: Optional[str]) -> Optional[Decimal]:
        """Parse dv String"""
        if dv_str is not None:
            parsed_string = re.search(self.DV_REGEX, dv_str)
            if parsed_string is not None:
                return self._parse_basic_decimal_str(parsed_string.group(1))
            else:
                return None
        else:
            return None

    def _parse_ml_str(self, ml_str: Optional[str]) -> Optional[Decimal]:
        """Parse ml string"""
        if ml_str is not None:
            parsed_string = re.search(self.ML_REGEX, ml_str)
            if parsed_string is not None:
                return self._parse_basic_decimal_str(parsed_string.group(1))
            else:
                return None
        else:
            return None

    def _parse_iso_dur_str(self, iso_str: Optional[str]) -> Optional[Decimal]:
        """Parse iso duration strings"""
        if iso_str is None:
            return None
        parsed_string1 = re.search(self.ISO_DUR_REGEX1, iso_str)
        if parsed_string1 is None:
            parsed_string2 = re.search(self.ISO_DUR_REGEX2, iso_str)
            if parsed_string2 is None:
                return None
            else:
                hours = self._parse_basic_decimal_str(parsed_string2.group(1))
                minutes = self._parse_basic_decimal_str(
                    parsed_string2.group(2)
                )
                if hours is not None and minutes is not None and hours < 5.0:
                    return 60 * hours + minutes
                else:
                    return None
        else:
            hours = self._parse_basic_decimal_str(parsed_string1.group(1))
            if hours is not None and hours < 5.0:
                return 60 * hours
            else:
                return None

    def _parse_weight_str(
        self, weight_str: Optional[str]
    ) -> Optional[Decimal]:
        """Parse weight strings"""
        # Most entries are recorded as simple floats. There are some outliers.
        # But we can map those to None for the time being.
        parsed_weight = self._parse_basic_decimal_str(weight_str)
        if parsed_weight is not None and parsed_weight < 1000:
            return parsed_weight
        else:
            return None

    def _parse_alt_time_str(
        self, alt_time_str: Optional[str]
    ) -> Optional[Decimal]:
        """Parse alternating time strings"""
        if alt_time_str is not None:
            parsed_string = re.search(self.ALT_TIME_REGEX, alt_time_str)
            if parsed_string is not None:
                return self._parse_basic_decimal_str(parsed_string.group(1))
            else:
                return None
        else:
            return None

    def _parse_angle_str(
        self, inj_ang_str: Optional[str]
    ) -> Optional[Decimal]:
        """Parse angle strings"""
        if inj_ang_str is not None:
            parsed_string = re.search(self.INJ_ANGLE_REGEX, inj_ang_str)
            if parsed_string is not None:
                return self._parse_basic_decimal_str(parsed_string.group(1))
            else:
                return None
        else:
            return None

    def _parse_current_str(self, cur_str: Optional[str]) -> Optional[Decimal]:
        """Parse current strings"""
        if cur_str is not None:
            parsed_string = re.search(self.CURRENT_REGEX, cur_str)
            if parsed_string is not None:
                return self._parse_basic_decimal_str(parsed_string.group(1))
            else:
                return None
        else:
            return None

    def _parse_length_of_time_str(
        self, len_of_time_str: Optional[str]
    ) -> Optional[Decimal]:
        """Parse length of time strings"""
        if len_of_time_str is not None:
            parsed_string = re.search(
                self.LENGTH_OF_TIME_REGEX, len_of_time_str
            )
            if parsed_string is not None:
                return self._parse_basic_decimal_str(parsed_string.group(1))
            else:
                return None
        else:
            return None

    @staticmethod
    def _parse_virus_strain_str(_: Optional[str]) -> Optional[str]:
        """Parse virus strain strings"""
        # TODO: Figure out how to parse virus strain fields
        return None

    def _parse_inj_vol_str(self, vol_str: Optional[str]) -> Optional[Decimal]:
        """Parse injection volume strings"""
        if vol_str is not None:
            parsed_string = re.search(self.VOLUME_REGEX, vol_str)
            if parsed_string is not None:
                return self._parse_basic_decimal_str(parsed_string.group(1))
            else:
                return None
        else:
            return None

    @staticmethod
    def _parse_datetime_to_date(dt: Optional[datetime]) -> Optional[date]:
        """Parse date from datetime"""
        return None if dt is None else dt.date()

    @property
    def aind_age_at_injection(self) -> Optional[str]:
        """Maps age_at_injection to aind model"""
        return self._nsb.age_at_injection

    @property
    def aind_ap2nd_inj(self) -> Optional[Decimal]:
        """Maps ap2nd_inj to aind model"""
        return self._parse_ap_str(self._nsb.ap2nd_inj)

    @property
    def aind_author_id(self) -> Optional[int]:
        """Maps author_id to aind model"""
        return self._nsb.author_id

    @property
    def aind_breg2_lamb(self) -> Optional[Decimal]:
        """Maps breg2_lamb to aind model"""
        return self._parse_basic_decimal_str(self._nsb.breg2_lamb)

    @property
    def aind_cage(self) -> Optional[str]:
        """Maps cage to aind model"""
        return self._nsb.cage

    @property
    def aind_color_tag(self) -> Optional[str]:
        """Maps color_tag to aind model"""
        return self._nsb.color_tag

    @property
    def aind_compliance_asset_id(self) -> Optional[str]:
        """Maps compliance_asset_id to aind model"""
        return self._nsb.compliance_asset_id

    @property
    def aind_craniotomy_type(self) -> Optional[CraniotomyType]:
        """Maps craniotomy_type to aind model"""
        return (
            CraniotomyType.OTHER
            if self._nsb.craniotomy_type is None
            else {
                self._nsb.craniotomy_type.SELECT: CraniotomyType.OTHER,
                self._nsb.craniotomy_type.VISUAL_CORTEX_5MM: (
                    CraniotomyType.VISCTX
                ),
                self._nsb.craniotomy_type.FRONTAL_WINDOW_3MM: (
                    CraniotomyType.THREE_MM
                ),
                self._nsb.craniotomy_type.WHC_NP: CraniotomyType.WHC,
                self._nsb.craniotomy_type.WHC_2_P: CraniotomyType.WHC,
            }.get(self._nsb.craniotomy_type, CraniotomyType.OTHER)
        )

    @property
    def aind_created(self) -> Optional[datetime]:
        """Maps created to aind model"""
        return self._nsb.created

    @property
    def aind_date1st_injection(self) -> Optional[date]:
        """Maps date1st_injection to aind model"""
        return self._parse_datetime_to_date(self._nsb.date1st_injection)

    @property
    def aind_date2nd_injection(self) -> Optional[date]:
        """Maps date2nd_injection to aind model"""
        return self._parse_datetime_to_date(self._nsb.date2nd_injection)

    @property
    def aind_date_of_birth(self) -> Optional[date]:
        """Maps date_of_birth to aind model"""
        return self._parse_datetime_to_date(self._nsb.date_of_birth)

    @property
    def aind_date_of_surgery(self) -> Optional[date]:
        """Maps date_of_surgery to aind model"""
        return self._parse_datetime_to_date(self._nsb.date_of_surgery)

    @property
    def aind_date_range_end(self) -> Optional[datetime]:
        """Maps date_range_end to aind model"""
        return self._nsb.date_range_end

    @property
    def aind_date_range_start(self) -> Optional[datetime]:
        """Maps date_range_start to aind model"""
        return self._nsb.date_range_start

    @property
    def aind_dv2nd_inj(self) -> Optional[List[Decimal]]:
        """Maps dv2nd_inj to aind model"""
        dv = self._parse_dv_str(self._nsb.dv2nd_inj)
        return [] if dv is None else [dv]

    @property
    def aind_editor_id(self) -> Optional[int]:
        """Maps editor_id to aind model"""
        return self._nsb.editor_id

    @property
    def aind_end_of_week(self) -> Optional[datetime]:
        """Maps end_of_week to aind model"""
        return self._nsb.end_of_week

    @property
    def aind_fiber_implant1(self) -> Optional[bool]:
        """Maps fiber_implant1 to aind model"""
        return self._nsb.fiber_implant1

    @property
    def aind_fiber_implant1_dv(self) -> Optional[Decimal]:
        """Maps fiber_implant1_dv to aind model"""
        return self._parse_dv_str(self._nsb.fiber_implant1_dv)

    @property
    def aind_fiber_implant2(self) -> Optional[bool]:
        """Maps fiber_implant2 to aind model"""
        return self._nsb.fiber_implant2

    @property
    def aind_fiber_implant2_dv(self) -> Optional[Decimal]:
        """Maps fiber_implant2_dv to aind model"""
        return self._parse_dv_str(self._nsb.fiber_implant2_dv)

    @property
    def aind_field30(self) -> Optional[str]:
        """Maps field30 to aind model"""
        return self._nsb.field30

    @property
    def aind_field50(self) -> Optional[str]:
        """Maps field50 to aind model"""
        return self._nsb.field50

    @property
    def aind_first_inj_recovery(self) -> Optional[Decimal]:
        """Maps first_inj_recovery to aind model"""
        opt_float = self._nsb.first_inj_recovery
        return None if opt_float is None else Decimal(opt_float)

    @property
    def aind_first_injection_iso_durat(self) -> Optional[Decimal]:
        """Maps first_injection_iso_durat to aind model"""
        return self._parse_iso_dur_str(self._nsb.first_injection_iso_durat)

    @property
    def aind_first_injection_weight_af(self) -> Optional[Decimal]:
        """Maps first_injection_weight_af to aind model"""
        return self._parse_weight_str(self._nsb.first_injection_weight_af)

    @property
    def aind_first_injection_weight_be(self) -> Optional[Decimal]:
        """Maps first_injection_weight_be to aind model"""
        return self._parse_weight_str(self._nsb.first_injection_weight_be)

    @property
    def aind_headpost_type(self) -> HeadPostInfo:
        """Maps headpost_type to aind model"""
        return (
            HeadPostInfo()
            if self._nsb.headpost_type is None
            else {
                self._nsb.headpost_type.SELECT: HeadPostInfo(),
                self._nsb.headpost_type.CAMSTYLE_HEADFRAME_016010: (
                    HeadPostInfo(
                        headframe_type="CAM-style",
                        headframe_part_number="0160-100-10 Rev A",
                        well_type="CAM-style",
                    )
                ),
                self._nsb.headpost_type.NEUROPIXELSTYLE_HEADFRAME: (
                    HeadPostInfo(
                        headframe_type="Neuropixel-style",
                        headframe_part_number="0160-100-10",
                        well_type="Neuropixel-style",
                        well_part_number="0160-200-36",
                    )
                ),
                self._nsb.headpost_type.MESOSCOPESTYLE_WELL_WITH: (
                    HeadPostInfo(
                        headframe_type="NGC-style",
                        headframe_part_number="0160-100-10",
                        well_type="Mesoscope-style",
                        well_part_number="0160-200-20",
                    )
                ),
                self._nsb.headpost_type.WHC_42_WITH_NEUROPIXEL_WE: (
                    HeadPostInfo(
                        headframe_type="WHC #42",
                        headframe_part_number="42",
                        well_type="Neuropixel-style",
                        well_part_number="0160-200-36",
                    )
                ),
                self._nsb.headpost_type.NGCSTYLE_HEADFRAME_NO_WEL: (
                    HeadPostInfo(
                        headframe_type="NGC-style",
                        headframe_part_number="0160-100-10",
                    )
                ),
                self._nsb.headpost_type.AI_STRAIGHT_HEADBAR: (
                    HeadPostInfo(headframe_type="AI Straight Headbar")
                ),
            }.get(self._nsb.headpost_type, HeadPostInfo())
        )

    @property
    def aind_hemisphere2nd_inj(self) -> Optional[Side]:
        """Maps hemisphere2nd_inj to aind model"""
        return (
            None
            if self._nsb.hemisphere2nd_inj is None
            else {
                self._nsb.hemisphere2nd_inj.SELECT: None,
                self._nsb.hemisphere2nd_inj.LEFT: Side.LEFT,
                self._nsb.hemisphere2nd_inj.RIGHT: Side.RIGHT,
            }.get(self._nsb.hemisphere2nd_inj, None)
        )

    @property
    def aind_hp_a_p(self) -> Optional[Decimal]:
        """Maps hp_a_p to aind model"""
        return self._parse_ap_str(self._nsb.hp_a_p)

    @property
    def aind_hp_diameter(self) -> Optional[str]:
        """Maps hp_diameter to aind model"""
        return self._nsb.hp_diameter

    @property
    def aind_hp_durotomy(self) -> Optional[bool]:
        """Maps hp_durotomy to aind model"""
        return {
            self._nsb.hp_durotomy.SELECT: None,
            self._nsb.hp_durotomy.YES: True,
            self._nsb.hp_durotomy.NO: False,
        }.get(self._nsb.hp_durotomy, None)

    @property
    def aind_hp_inj(self) -> Optional[bool]:
        """Maps hp_inj to aind model"""
        return (
            True
            if self._nsb.hp_inj is not None and self._nsb.hp_inj == "Yes"
            else False
        )

    @property
    def aind_hp_iso_level(self) -> Optional[Decimal]:
        """Maps hp_iso_level to aind model"""
        return (
            None
            if self._nsb.hp_iso_level is None
            else {
                self._nsb.hp_iso_level.SELECT: None,
                self._nsb.hp_iso_level.N_025: Decimal(0.25),
                self._nsb.hp_iso_level.N_050: Decimal(0.50),
                self._nsb.hp_iso_level.N_075: Decimal(0.75),
                self._nsb.hp_iso_level.N_100: Decimal(1.00),
                self._nsb.hp_iso_level.N_125: Decimal(1.25),
                self._nsb.hp_iso_level.N_15: Decimal(1.5),
                self._nsb.hp_iso_level.N_175: Decimal(1.75),
                self._nsb.hp_iso_level.N_200: Decimal(2.00),
                self._nsb.hp_iso_level.N_225: Decimal(2.25),
                self._nsb.hp_iso_level.N_250: Decimal(2.50),
                self._nsb.hp_iso_level.N_275: Decimal(2.75),
                self._nsb.hp_iso_level.N_300: None,
            }.get(self._nsb.hp_iso_level, None)
        )

    @property
    def aind_hp_loc(self) -> Optional[Side]:
        """Maps hp_loc to aind model"""
        return (
            None
            if self._nsb.hp_loc is None
            else {
                self._nsb.hp_loc.SELECT: None,
                self._nsb.hp_loc.LEFT: Side.LEFT,
                self._nsb.hp_loc.CENTER: None,
                self._nsb.hp_loc.RIGHT: Side.RIGHT,
            }.get(self._nsb.hp_loc, None)
        )

    @property
    def aind_hp_m_l(self) -> Optional[Decimal]:
        """Maps hp_m_l to aind model"""
        return self._parse_ml_str(self._nsb.hp_m_l)

    @property
    def aind_hp_recovery(self) -> Optional[float]:
        """Maps hp_recovery to aind model"""
        opt_float = self._nsb.hp_recovery
        return None if opt_float is None else Decimal(self._nsb.hp_recovery)

    @property
    def aind_hp_requestor(self) -> Optional[str]:
        """Maps hp_requestor to aind model"""
        return self._nsb.hp_requestor

    @property
    def aind_hp_requestor_comments_pla(self) -> Optional[str]:
        """Maps hp_requestor_comments_pla to aind model"""
        return self._nsb.hp_requestor_comments_pla

    @property
    def aind_hp_surgeon_comments(self) -> Optional[str]:
        """Maps hp_surgeon_comments to aind model"""
        return self._nsb.hp_surgeon_comments

    @property
    def aind_hp_work_station(self) -> Optional[str]:
        """Maps hp_work_station to aind model"""
        return (
            None
            if self._nsb.hp_work_station is None
            else {
                self._nsb.hp_work_station.SELECT: None,
                self._nsb.hp_work_station.SWS_1: (
                    self._nsb.hp_work_station.SWS_1.value
                ),
                self._nsb.hp_work_station.SWS_2: (
                    self._nsb.hp_work_station.SWS_2.value
                ),
                self._nsb.hp_work_station.SWS_3: (
                    self._nsb.hp_work_station.SWS_3.value
                ),
                self._nsb.hp_work_station.SWS_4: (
                    self._nsb.hp_work_station.SWS_4.value
                ),
                self._nsb.hp_work_station.SWS_5: (
                    self._nsb.hp_work_station.SWS_5.value
                ),
                self._nsb.hp_work_station.SWS_6: (
                    self._nsb.hp_work_station.SWS_6.value
                ),
                self._nsb.hp_work_station.SWS_7: (
                    self._nsb.hp_work_station.SWS_7.value
                ),
                self._nsb.hp_work_station.SWS_8: (
                    self._nsb.hp_work_station.SWS_8.value
                ),
                self._nsb.hp_work_station.SWS_9: (
                    self._nsb.hp_work_station.SWS_9.value
                ),
            }.get(self._nsb.hp_work_station, None)
        )

    @property
    def aind_iacuc_protocol(self) -> Optional[str]:
        """Maps iacuc_protocol to aind model"""
        return (
            None
            if self._nsb.iacuc_protocol is None
            else {
                self._nsb.iacuc_protocol.SELECT: None,
                self._nsb.iacuc_protocol.N_2001: (
                    self._nsb.iacuc_protocol.N_2001.value
                ),
                self._nsb.iacuc_protocol.N_2002: (
                    self._nsb.iacuc_protocol.N_2002.value
                ),
                self._nsb.iacuc_protocol.N_2003: (
                    self._nsb.iacuc_protocol.N_2003.value
                ),
                self._nsb.iacuc_protocol.N_2004: (
                    self._nsb.iacuc_protocol.N_2004.value
                ),
                self._nsb.iacuc_protocol.N_2005: (
                    self._nsb.iacuc_protocol.N_2005.value
                ),
                self._nsb.iacuc_protocol.N_2006: (
                    self._nsb.iacuc_protocol.N_2006.value
                ),
                self._nsb.iacuc_protocol.N_2011: (
                    self._nsb.iacuc_protocol.N_2011.value
                ),
                self._nsb.iacuc_protocol.N_2102: (
                    self._nsb.iacuc_protocol.N_2102.value
                ),
                self._nsb.iacuc_protocol.N_2103: (
                    self._nsb.iacuc_protocol.N_2103.value
                ),
                self._nsb.iacuc_protocol.N_2104: (
                    self._nsb.iacuc_protocol.N_2104.value
                ),
                self._nsb.iacuc_protocol.N_2105: (
                    self._nsb.iacuc_protocol.N_2105.value
                ),
                self._nsb.iacuc_protocol.N_2106: (
                    self._nsb.iacuc_protocol.N_2106.value
                ),
                self._nsb.iacuc_protocol.N_2107: (
                    self._nsb.iacuc_protocol.N_2107.value
                ),
                self._nsb.iacuc_protocol.N_2108: (
                    self._nsb.iacuc_protocol.N_2108.value
                ),
                self._nsb.iacuc_protocol.N_2109: (
                    self._nsb.iacuc_protocol.N_2109.value
                ),
                self._nsb.iacuc_protocol.N_2110: (
                    self._nsb.iacuc_protocol.N_2110.value
                ),
                self._nsb.iacuc_protocol.N_2113: (
                    self._nsb.iacuc_protocol.N_2113.value
                ),
                self._nsb.iacuc_protocol.N_2115: (
                    self._nsb.iacuc_protocol.N_2115.value
                ),
                self._nsb.iacuc_protocol.N_2117: (
                    self._nsb.iacuc_protocol.N_2117.value
                ),
                self._nsb.iacuc_protocol.N_2201: (
                    self._nsb.iacuc_protocol.N_2201.value
                ),
                self._nsb.iacuc_protocol.N_2202: (
                    self._nsb.iacuc_protocol.N_2202.value
                ),
                self._nsb.iacuc_protocol.N_2205: (
                    self._nsb.iacuc_protocol.N_2205.value
                ),
                self._nsb.iacuc_protocol.N_2212: (
                    self._nsb.iacuc_protocol.N_2212.value
                ),
            }.get(self._nsb.iacuc_protocol, None)
        )

    @property
    def aind_id(self) -> Optional[int]:
        """Maps id to aind model"""
        return self._nsb.id

    @property
    def aind_inj1_alternating_time(self) -> Optional[str]:
        """Maps inj1_alternating_time to aind model"""
        return self._nsb.inj1_alternating_time

    @property
    def aind_inj1_angle_v2(self) -> Optional[Decimal]:
        """Maps inj1_angle_v2 to aind model"""
        return self._parse_angle_str(self._nsb.inj1_angle_v2)

    @property
    def aind_inj1_current(self) -> Optional[Decimal]:
        """Maps inj1_current to aind model"""
        return self._parse_current_str(self._nsb.inj1_current)

    @property
    def aind_inj1_lenghtof_time(self) -> Optional[Decimal]:
        """Maps inj1_lenghtof_time to aind model"""
        return self._parse_length_of_time_str(self._nsb.inj1_lenghtof_time)

    @property
    def aind_inj1_round(self) -> Optional[InjectionRound]:
        """Maps inj1_round to aind model"""
        return (
            None
            if self._nsb.inj1_round is None
            else {
                self._nsb.inj1_round.SELECT: None,
                self._nsb.inj1_round.N_1ST: InjectionRound.FIRST,
                self._nsb.inj1_round.N_2ND: InjectionRound.SECOND,
                self._nsb.inj1_round.NA: None,
            }.get(self._nsb.inj1_round, None)
        )

    @property
    def aind_inj1_storage_location(self) -> Optional[str]:
        """Maps inj1_storage_location to aind model"""
        return self._nsb.inj1_storage_location

    @property
    def aind_inj1_type(self) -> Optional[InjectionType]:
        """Maps inj1_type to aind model"""
        return (
            None
            if self._nsb.inj1_type is None
            else {
                self._nsb.inj1_type.SELECT: None,
                self._nsb.inj1_type.IONTOPHORESIS: InjectionType.IONTOPHORESIS,
                self._nsb.inj1_type.NANOJECT_PRESSURE: InjectionType.NANOJECT,
            }.get(self._nsb.inj1_type, None)
        )

    @property
    def aind_inj1_virus_strain_rt(self) -> Optional[str]:
        """Maps inj1_virus_strain_rt to aind model"""
        return self._parse_virus_strain_str(self._nsb.inj1_virus_strain_rt)

    @property
    def aind_inj1_vol(self) -> Optional[List[Decimal]]:
        """Maps inj1_vol to aind model"""
        vol = self._parse_inj_vol_str(self._nsb.inj1_vol)
        return None if vol is None else [vol]

    @property
    def aind_inj1angle0(self) -> Optional[Decimal]:
        """Maps inj1angle0 to aind model"""
        return (
            None
            if self._nsb.inj1angle0 is None
            else {
                self._nsb.inj1angle0.SELECT: None,
                self._nsb.inj1angle0.N_0_DEGREES: Decimal(0),
                self._nsb.inj1angle0.N_10_DEGREES: Decimal(10),
                self._nsb.inj1angle0.N_15_DEGREES: Decimal(15),
                self._nsb.inj1angle0.N_20_DEGREES: Decimal(20),
                self._nsb.inj1angle0.N_30_DEGREES: Decimal(30),
                self._nsb.inj1angle0.N_40_DEGREES: Decimal(40),
            }.get(self._nsb.inj1angle0, None)
        )

    @property
    def aind_inj1volperdepth(self) -> Optional[List[Decimal]]:
        """Maps inj1volperdepth to aind model"""
        volperdepth = self._parse_inj_vol_str(self._nsb.inj1volperdepth)
        return None if volperdepth is None else [volperdepth]

    @property
    def aind_inj2_alternating_time(self) -> Optional[str]:
        """Maps inj2_alternating_time to aind model"""
        return self._nsb.inj2_alternating_time

    @property
    def aind_inj2_angle_v2(self) -> Optional[Decimal]:
        """Maps inj2_angle_v2 to aind model"""
        return self._parse_angle_str(self._nsb.inj2_angle_v2)

    @property
    def aind_inj2_current(self) -> Optional[Decimal]:
        """Maps inj2_current to aind model"""
        return self._parse_current_str(self._nsb.inj2_current)

    @property
    def aind_inj2_lenghtof_time(self) -> Optional[Decimal]:
        """Maps inj2_lenghtof_time to aind model"""
        return self._parse_length_of_time_str(self._nsb.inj2_lenghtof_time)

    @property
    def aind_inj2_round(self) -> Optional[InjectionRound]:
        """Maps inj2_round to aind model"""
        return (
            None
            if self._nsb.inj2_round is None
            else {
                self._nsb.inj2_round.SELECT: None,
                self._nsb.inj2_round.N_1ST: InjectionRound.FIRST,
                self._nsb.inj2_round.N_2ND: InjectionRound.SECOND,
                self._nsb.inj2_round.NA: None,
            }.get(self._nsb.inj2_round, None)
        )

    @property
    def aind_inj2_storage_location(self) -> Optional[str]:
        """Maps inj2_storage_location to aind model"""
        return self._nsb.inj2_storage_location

    @property
    def aind_inj2_type(self) -> Optional[InjectionType]:
        """Maps inj2_type to aind model"""
        return (
            None
            if self._nsb.inj2_type is None
            else {
                self._nsb.inj2_type.SELECT: None,
                self._nsb.inj2_type.IONTOPHORESIS: InjectionType.IONTOPHORESIS,
                self._nsb.inj2_type.NANOJECT_PRESSURE: InjectionType.NANOJECT,
            }.get(self._nsb.inj2_type, None)
        )

    @property
    def aind_inj2_virus_strain_rt(self) -> Optional[str]:
        """Maps inj2_virus_strain_rt to aind model"""
        return self._parse_virus_strain_str(self._nsb.inj2_virus_strain_rt)

    @property
    def aind_inj2_vol(self) -> Optional[List[Decimal]]:
        """Maps inj2_vol to aind model"""
        vol = self._parse_inj_vol_str(self._nsb.inj2_vol)
        return None if vol is None else [vol]

    @property
    def aind_inj2angle0(self) -> Optional[Decimal]:
        """Maps inj2angle0 to aind model"""
        return (
            None
            if self._nsb.inj2angle0 is None
            else {
                self._nsb.inj2angle0.SELECT: None,
                self._nsb.inj2angle0.N_0_DEGREES: Decimal(0),
                self._nsb.inj2angle0.N_10_DEGREES: Decimal(10),
                self._nsb.inj2angle0.N_15_DEGREES: Decimal(15),
                self._nsb.inj2angle0.N_20_DEGREES: Decimal(20),
                self._nsb.inj2angle0.N_30_DEGREES: Decimal(30),
                self._nsb.inj2angle0.N_40_DEGREES: Decimal(40),
            }.get(self._nsb.inj2angle0, None)
        )

    @property
    def aind_inj2volperdepth(self) -> Optional[List[Decimal]]:
        """Maps inj2volperdepth to aind model"""
        volperdepth = self._parse_inj_vol_str(self._nsb.inj2volperdepth)
        return None if volperdepth is None else [volperdepth]

    @property
    def aind_ionto_number_hpinj(self) -> Optional[str]:
        """Maps ionto_number_hpinj to aind model"""
        return (
            None
            if self._nsb.ionto_number_hpinj is None
            else {
                self._nsb.ionto_number_hpinj.IONTO_1: (
                    self._nsb.ionto_number_hpinj.IONTO_1.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_2: (
                    self._nsb.ionto_number_hpinj.IONTO_2.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_3: (
                    self._nsb.ionto_number_hpinj.IONTO_3.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_4: (
                    self._nsb.ionto_number_hpinj.IONTO_4.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_5: (
                    self._nsb.ionto_number_hpinj.IONTO_5.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_6: (
                    self._nsb.ionto_number_hpinj.IONTO_6.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_7: (
                    self._nsb.ionto_number_hpinj.IONTO_7.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_8: (
                    self._nsb.ionto_number_hpinj.IONTO_8.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_9: (
                    self._nsb.ionto_number_hpinj.IONTO_9.value
                ),
            }.get(self._nsb.ionto_number_hpinj, None)
        )

    @property
    def aind_ionto_number_inj1(self) -> Optional[str]:
        """Maps ionto_number_inj1 to aind model"""
        return (
            None
            if self._nsb.ionto_number_inj1 is None
            else {
                self._nsb.ionto_number_inj1.SELECT: None,
                self._nsb.ionto_number_inj1.IONTO_1: (
                    self._nsb.ionto_number_inj1.IONTO_1.value
                ),
                self._nsb.ionto_number_inj1.IONTO_2: (
                    self._nsb.ionto_number_inj1.IONTO_2.value
                ),
                self._nsb.ionto_number_inj1.IONTO_3: (
                    self._nsb.ionto_number_inj1.IONTO_3.value
                ),
                self._nsb.ionto_number_inj1.IONTO_4: (
                    self._nsb.ionto_number_inj1.IONTO_4.value
                ),
                self._nsb.ionto_number_inj1.IONTO_5: (
                    self._nsb.ionto_number_inj1.IONTO_5.value
                ),
                self._nsb.ionto_number_inj1.IONTO_6: (
                    self._nsb.ionto_number_inj1.IONTO_6.value
                ),
                self._nsb.ionto_number_inj1.IONTO_7: (
                    self._nsb.ionto_number_inj1.IONTO_7.value
                ),
                self._nsb.ionto_number_inj1.IONTO_8: (
                    self._nsb.ionto_number_inj1.IONTO_8.value
                ),
                self._nsb.ionto_number_inj1.IONTO_9: (
                    self._nsb.ionto_number_inj1.IONTO_9.value
                ),
                self._nsb.ionto_number_inj1.IONTO_10: (
                    self._nsb.ionto_number_inj1.IONTO_10.value
                ),
                self._nsb.ionto_number_inj1.NA: None,
            }.get(self._nsb.ionto_number_inj1, None)
        )

    @property
    def aind_ionto_number_inj2(self) -> Optional[str]:
        """Maps ionto_number_inj2 to aind model"""
        return (
            None
            if self._nsb.ionto_number_inj2 is None
            else {
                self._nsb.ionto_number_inj2.SELECT: None,
                self._nsb.ionto_number_inj2.IONTO_1: (
                    self._nsb.ionto_number_inj2.IONTO_1.value
                ),
                self._nsb.ionto_number_inj2.IONTO_2: (
                    self._nsb.ionto_number_inj2.IONTO_2.value
                ),
                self._nsb.ionto_number_inj2.IONTO_3: (
                    self._nsb.ionto_number_inj2.IONTO_3.value
                ),
                self._nsb.ionto_number_inj2.IONTO_4: (
                    self._nsb.ionto_number_inj2.IONTO_4.value
                ),
                self._nsb.ionto_number_inj2.IONTO_5: (
                    self._nsb.ionto_number_inj2.IONTO_5.value
                ),
                self._nsb.ionto_number_inj2.IONTO_6: (
                    self._nsb.ionto_number_inj2.IONTO_6.value
                ),
                self._nsb.ionto_number_inj2.IONTO_7: (
                    self._nsb.ionto_number_inj2.IONTO_7.value
                ),
                self._nsb.ionto_number_inj2.IONTO_8: (
                    self._nsb.ionto_number_inj2.IONTO_8.value
                ),
                self._nsb.ionto_number_inj2.IONTO_9: (
                    self._nsb.ionto_number_inj2.IONTO_9.value
                ),
                self._nsb.ionto_number_inj2.IONTO_10: (
                    self._nsb.ionto_number_inj2.IONTO_10.value
                ),
                self._nsb.ionto_number_inj2.NA: None,
            }.get(self._nsb.ionto_number_inj2, None)
        )

    @property
    def aind_iso_on(self) -> Optional[Decimal]:
        """Maps iso_on to aind model"""
        opt_float = self._nsb.iso_on
        return None if opt_float is None else Decimal(opt_float)

    @property
    def aind_lab_tracks_group(self) -> Optional[str]:
        """Maps lab_tracks_group to aind model"""
        return self._nsb.lab_tracks_group

    @property
    def aind_lab_tracks_id(self) -> Optional[str]:
        """Maps lab_tracks_id to aind model"""
        return self._nsb.lab_tracks_id

    @property
    def aind_lab_tracks_requestor(self) -> Optional[str]:
        """Maps lab_tracks_requestor to aind model"""
        return self._nsb.lab_tracks_requestor

    @property
    def aind_lims_link(self) -> Optional[str]:
        """Maps lims_link to aind model"""
        return self._nsb.lims_link

    @property
    def aind_long1st_round_inj_cmts(self) -> Optional[str]:
        """Maps long1st_round_inj_cmts to aind model"""
        return self._nsb.long1st_round_inj_cmts

    @property
    def aind_long2nd_rnd_inj_cmts(self) -> Optional[str]:
        """Maps long2nd_rnd_inj_cmts to aind model"""
        return self._nsb.long2nd_rnd_inj_cmts

    @property
    def aind_long_requestor_comments(self) -> Optional[str]:
        """Maps long_requestor_comments to aind model"""
        return self._nsb.long_requestor_comments

    @property
    def aind_long_surgeon_comments(self) -> Optional[str]:
        """Maps long_surgeon_comments to aind model"""
        return self._nsb.long_surgeon_comments

    @property
    def aind_ml2nd_inj(self) -> Optional[Decimal]:
        """Maps ml2nd_inj to aind model"""
        return self._parse_ml_str(self._nsb.ml2nd_inj)

    @property
    def aind_modified(self) -> Optional[datetime]:
        """Maps modified to aind model"""
        return self._nsb.modified

    @property
    def aind_nanoject_number_inj10(self) -> Optional[str]:
        """Maps nanoject_number_inj10 to aind model"""
        return (
            None
            if self._nsb.nanoject_number_inj10 is None
            else {
                self._nsb.nanoject_number_inj10.SELECT: None,
                self._nsb.nanoject_number_inj10.NJ1: (
                    self._nsb.nanoject_number_inj10.NJ1.value
                ),
                self._nsb.nanoject_number_inj10.NJ2: (
                    self._nsb.nanoject_number_inj10.NJ2.value
                ),
                self._nsb.nanoject_number_inj10.NJ3: (
                    self._nsb.nanoject_number_inj10.NJ3.value
                ),
                self._nsb.nanoject_number_inj10.NJ4: (
                    self._nsb.nanoject_number_inj10.NJ4.value
                ),
                self._nsb.nanoject_number_inj10.NJ5: (
                    self._nsb.nanoject_number_inj10.NJ5.value
                ),
                self._nsb.nanoject_number_inj10.NJ6: (
                    self._nsb.nanoject_number_inj10.NJ6.value
                ),
                self._nsb.nanoject_number_inj10.NJ7: (
                    self._nsb.nanoject_number_inj10.NJ7.value
                ),
                self._nsb.nanoject_number_inj10.NJ8: (
                    self._nsb.nanoject_number_inj10.NJ8.value
                ),
                self._nsb.nanoject_number_inj10.NA: None,
            }.get(self._nsb.nanoject_number_inj10, None)
        )

    @property
    def aind_nanoject_number_inj2(self) -> Optional[str]:
        """Maps nanoject_number_inj2 to aind model"""
        return (
            None
            if self._nsb.nanoject_number_inj2 is None
            else {
                self._nsb.nanoject_number_inj2.SELECT: None,
                self._nsb.nanoject_number_inj2.NJ1: (
                    self._nsb.nanoject_number_inj2.NJ1
                ),
                self._nsb.nanoject_number_inj2.NJ2: (
                    self._nsb.nanoject_number_inj2.NJ2
                ),
                self._nsb.nanoject_number_inj2.NJ3: (
                    self._nsb.nanoject_number_inj2.NJ3
                ),
                self._nsb.nanoject_number_inj2.NJ4: (
                    self._nsb.nanoject_number_inj2.NJ4
                ),
                self._nsb.nanoject_number_inj2.NJ5: (
                    self._nsb.nanoject_number_inj2.NJ5
                ),
                self._nsb.nanoject_number_inj2.NJ6: (
                    self._nsb.nanoject_number_inj2.NJ6
                ),
                self._nsb.nanoject_number_inj2.NJ7: (
                    self._nsb.nanoject_number_inj2.NJ7
                ),
                self._nsb.nanoject_number_inj2.NJ8: (
                    self._nsb.nanoject_number_inj2.NJ8
                ),
                self._nsb.nanoject_number_inj2.NA: None,
            }.get(self._nsb.nanoject_number_inj2, None)
        )

    @property
    def aind_nd_roung_injection_commen(self) -> Optional[str]:
        """Maps nd_roung_injection_commen to aind model"""
        return self._nsb.nd_roung_injection_commen

    @property
    def aind_pedigree_name(self) -> Optional[str]:
        """Maps pedigree_name to aind model"""
        return self._nsb.pedigree_name

    @property
    def aind_procedure(self) -> Optional[NSBProcedure]:
        """Maps procedure to aind model"""
        return self._nsb.procedure

    @property
    def aind_round1_inj_isolevel(self) -> Optional[Decimal]:
        """Maps round1_inj_isolevel to aind model"""
        return (
            None
            if self._nsb.round1_inj_isolevel is None
            else {
                self._nsb.round1_inj_isolevel.SELECT: None,
                self._nsb.round1_inj_isolevel.N_025: Decimal(0.25),
                self._nsb.round1_inj_isolevel.N_050: Decimal(0.50),
                self._nsb.round1_inj_isolevel.N_075: Decimal(0.75),
                self._nsb.round1_inj_isolevel.N_100: Decimal(1.00),
                self._nsb.round1_inj_isolevel.N_125: Decimal(1.25),
                self._nsb.round1_inj_isolevel.N_150: Decimal(1.50),
                self._nsb.round1_inj_isolevel.N_175: Decimal(1.75),
                self._nsb.round1_inj_isolevel.N_200: Decimal(2.00),
                self._nsb.round1_inj_isolevel.N_225: Decimal(2.25),
                self._nsb.round1_inj_isolevel.N_250: Decimal(2.50),
                self._nsb.round1_inj_isolevel.N_275: Decimal(2.75),
                self._nsb.round1_inj_isolevel.N_300: None,
            }.get(self._nsb.round1_inj_isolevel, None)
        )

    @property
    def aind_round2_inj_isolevel(self) -> Optional[Decimal]:
        """Maps round2_inj_isolevel to aind model"""
        return (
            None
            if self._nsb.round2_inj_isolevel is None
            else {
                self._nsb.round2_inj_isolevel.SELECT: None,
                self._nsb.round2_inj_isolevel.N_025: Decimal(0.25),
                self._nsb.round2_inj_isolevel.N_050: Decimal(0.50),
                self._nsb.round2_inj_isolevel.N_075: Decimal(0.75),
                self._nsb.round2_inj_isolevel.N_100: Decimal(1.00),
                self._nsb.round2_inj_isolevel.N_125: Decimal(1.25),
                self._nsb.round2_inj_isolevel.N_150: Decimal(1.50),
                self._nsb.round2_inj_isolevel.N_175: Decimal(1.75),
                self._nsb.round2_inj_isolevel.N_200: Decimal(2.00),
                self._nsb.round2_inj_isolevel.N_225: Decimal(2.25),
                self._nsb.round2_inj_isolevel.N_250: Decimal(2.50),
                self._nsb.round2_inj_isolevel.N_275: Decimal(2.75),
                self._nsb.round2_inj_isolevel.N_300: None,
            }.get(self._nsb.round2_inj_isolevel, None)
        )

    @property
    def aind_second_inj_recover(self) -> Optional[Decimal]:
        """Maps second_inj_recover to aind model"""
        opt_float = self._nsb.second_inj_recover
        return (
            None
            if opt_float is None
            else Decimal(self._nsb.second_inj_recover)
        )

    @property
    def aind_second_injection_iso_dura(self) -> Optional[Decimal]:
        """Maps second_injection_iso_dura to aind model"""
        return self._parse_iso_dur_str(self._nsb.second_injection_iso_dura)

    @property
    def aind_second_injection_weight_a(self) -> Optional[Decimal]:
        """Maps second_injection_weight_a to aind model"""
        return self._parse_weight_str(self._nsb.second_injection_weight_a)

    @property
    def aind_second_injection_weight_b(self) -> Optional[Decimal]:
        """Maps second_injection_weight_b to aind model"""
        return self._parse_weight_str(self._nsb.second_injection_weight_b)

    @property
    def aind_sex(self) -> Optional[Sex]:
        """Maps sex to aind model"""
        return (
            None
            if self._nsb.sex is None
            else {
                self._nsb.sex.SELECT: None,
                self._nsb.sex.MALE: Sex.MALE,
                self._nsb.sex.FEMALE: Sex.FEMALE,
            }.get(self._nsb.sex, None)
        )

    @property
    def aind_st_round_injection_commen(self) -> Optional[str]:
        """Maps st_round_injection_commen to aind model"""
        return self._nsb.st_round_injection_commen

    @property
    def aind_start_of_week(self) -> Optional[datetime]:
        """Maps start_of_week to aind model"""
        return self._nsb.start_of_week

    @property
    def aind_title(self) -> Optional[str]:
        """Maps title to aind model"""
        return self._nsb.title

    @property
    def aind_touch_up_comp(self) -> Optional[datetime]:
        """Maps touch_up_comp to aind model"""
        return self._nsb.touch_up_comp

    @property
    def aind_touch_up_weight(self) -> Optional[Decimal]:
        """Maps touch_up_weight to aind model"""
        return self._parse_weight_str(self._nsb.touch_up_weight)

    @property
    def aind_ui_version_string(self) -> Optional[str]:
        """Maps ui_version_string to aind model"""
        return self._nsb.ui_version_string

    @property
    def aind_virus_a_p(self) -> Optional[Decimal]:
        """Maps virus_a_p to aind model"""
        return self._parse_ap_str(self._nsb.virus_a_p)

    @property
    def aind_virus_d_v(self) -> Optional[List[Decimal]]:
        """Maps virus_d_v to aind model"""
        dv = self._parse_dv_str(self._nsb.virus_d_v)
        return [] if dv is None else [dv]

    @property
    def aind_virus_hemisphere(self) -> Optional[Side]:
        """Maps virus_hemisphere to aind model"""
        return (
            None
            if self._nsb.virus_hemisphere is None
            else {
                self._nsb.virus_hemisphere.SELECT: None,
                self._nsb.virus_hemisphere.LEFT: Side.LEFT,
                self._nsb.virus_hemisphere.RIGHT: Side.RIGHT,
            }.get(self._nsb.virus_hemisphere, None)
        )

    @property
    def aind_virus_m_l(self) -> Optional[Decimal]:
        """Maps virus_m_l to aind model"""
        return self._parse_ml_str(self._nsb.virus_m_l)

    @property
    def aind_weight_after_surgery(self) -> Optional[Decimal]:
        """Maps weight_after_surgery to aind model"""
        return self._parse_weight_str(self._nsb.weight_after_surgery)

    @property
    def aind_weight_before_surger(self) -> Optional[Decimal]:
        """Maps weight_before_surger to aind model"""
        return self._parse_weight_str(self._nsb.weight_before_surger)

    @property
    def aind_work_station1st_injection(self) -> Optional[str]:
        """Maps work_station1st_injection to aind model"""
        return (
            None
            if self._nsb.work_station1st_injection is None
            else {
                self._nsb.work_station1st_injection.SELECT: None,
                self._nsb.work_station1st_injection.SWS_1: (
                    self._nsb.work_station1st_injection.SWS_1.value
                ),
                self._nsb.work_station1st_injection.SWS_2: (
                    self._nsb.work_station1st_injection.SWS_2.value
                ),
                self._nsb.work_station1st_injection.SWS_3: (
                    self._nsb.work_station1st_injection.SWS_3.value
                ),
                self._nsb.work_station1st_injection.SWS_4: (
                    self._nsb.work_station1st_injection.SWS_4.value
                ),
                self._nsb.work_station1st_injection.SWS_5: (
                    self._nsb.work_station1st_injection.SWS_5.value
                ),
                self._nsb.work_station1st_injection.SWS_6: (
                    self._nsb.work_station1st_injection.SWS_6.value
                ),
                self._nsb.work_station1st_injection.SWS_7: (
                    self._nsb.work_station1st_injection.SWS_7.value
                ),
                self._nsb.work_station1st_injection.SWS_8: (
                    self._nsb.work_station1st_injection.SWS_8.value
                ),
                self._nsb.work_station1st_injection.SWS_9: (
                    self._nsb.work_station1st_injection.SWS_9.value
                ),
            }.get(self._nsb.work_station1st_injection, None)
        )

    @property
    def aind_work_station2nd_injection(self) -> Optional[str]:
        """Maps work_station2nd_injection to aind model"""
        return (
            None
            if self._nsb.work_station2nd_injection is None
            else {
                self._nsb.work_station2nd_injection.SELECT: None,
                self._nsb.work_station2nd_injection.SWS_1: (
                    self._nsb.work_station2nd_injection.SWS_1.value
                ),
                self._nsb.work_station2nd_injection.SWS_2: (
                    self._nsb.work_station2nd_injection.SWS_2.value
                ),
                self._nsb.work_station2nd_injection.SWS_3: (
                    self._nsb.work_station2nd_injection.SWS_3.value
                ),
                self._nsb.work_station2nd_injection.SWS_4: (
                    self._nsb.work_station2nd_injection.SWS_4.value
                ),
                self._nsb.work_station2nd_injection.SWS_5: (
                    self._nsb.work_station2nd_injection.SWS_5.value
                ),
                self._nsb.work_station2nd_injection.SWS_6: (
                    self._nsb.work_station2nd_injection.SWS_6.value
                ),
                self._nsb.work_station2nd_injection.SWS_7: (
                    self._nsb.work_station2nd_injection.SWS_7.value
                ),
                self._nsb.work_station2nd_injection.SWS_8: (
                    self._nsb.work_station2nd_injection.SWS_8.value
                ),
                self._nsb.work_station2nd_injection.SWS_9: (
                    self._nsb.work_station2nd_injection.SWS_9.value
                ),
            }.get(self._nsb.work_station2nd_injection, None)
        )

    @property
    def aind_experimenter_full_name(self) -> str:
        """Map author id to experimenter name"""
        return (
            "NSB"
            if self.aind_author_id is None
            else f"NSB-{self.aind_author_id}"
        )

    @property
    def aind_anaesthetic_type(self) -> str:
        """Default anaesthetic type"""
        return "isoflurane"

    @property
    def aind_craniotomy_size(self) -> Optional[Decimal]:
        """Map craniotomy type to size in mm"""
        return (
            None
            if self.aind_craniotomy_type is None
            else {
                self.aind_craniotomy_type.FIVE_MM: Decimal(5),
                self.aind_craniotomy_type.THREE_MM: Decimal(3),
            }.get(self.aind_craniotomy_type, None)
        )

    @property
    def aind_craniotomy_coordinates_reference(
        self,
    ) -> Optional[CoordinateReferenceLocation]:
        """Map craniotomy type to CoordinateReferenceLocation"""
        return (
            None
            if self.aind_craniotomy_type is None
            else {
                self.aind_craniotomy_type.VISCTX: (
                    CoordinateReferenceLocation.LAMBDA
                )
            }.get(self.aind_craniotomy_type, None)
        )

    @property
    def aind_inj1_coordinates_reference(
        self,
    ) -> Optional[CoordinateReferenceLocation]:
        """Map nsb inj1 field to CoordinateReferenceLocation"""
        if (
            self._nsb.virus_a_p is not None
            and "LAMBDA" in self._nsb.virus_a_p.upper()
        ):
            return CoordinateReferenceLocation.LAMBDA
        else:
            return None

    @property
    def aind_inj2_coordinates_reference(
        self,
    ) -> Optional[CoordinateReferenceLocation]:
        """Map nsb inj2 field to CoordinateReferenceLocation"""
        if (
            self._nsb.ap2nd_inj is not None
            and "LAMBDA" in self._nsb.ap2nd_inj.upper()
        ):
            return CoordinateReferenceLocation.LAMBDA
        else:
            return None

    def get_head_frame_procedure(self) -> Headframe:
        """Get head frame procedure"""
        # Missing protocol_id
        return Headframe.model_construct(
            headframe_type=self.aind_headpost_type.headframe_type,
            headframe_part_number=(
                self.aind_headpost_type.headframe_part_number
            ),
            well_part_number=self.aind_headpost_type.well_part_number,
            well_type=self.aind_headpost_type.well_type,
        )

    def get_craniotomy_procedure(self) -> Craniotomy:
        """Get craniotomy procedure"""
        # Missing protocol_id
        return Craniotomy.model_construct(
            craniotomy_type=self.aind_craniotomy_type,
            craniotomy_hemisphere=self.aind_hp_loc,
            craniotomy_coordinates_ml=self.aind_hp_m_l,
            craniotomy_coordinates_ap=self.aind_hp_a_p,
            craniotomy_coordinates_reference=(
                self.aind_craniotomy_coordinates_reference
            ),
            bregma_to_lambda_distance=self.aind_breg2_lamb,
            craniotomy_size=self.aind_craniotomy_size,
            dura_removed=self.aind_hp_durotomy,
        )

    def get_first_injection_procedure(self) -> BrainInjection:
        """Get first injection procedure"""
        injection_materials = (
            []
            if self.aind_inj1_virus_strain_rt is None
            else [
                ViralMaterial.model_construct(
                    name=self.aind_inj1_virus_strain_rt
                )
            ]
        )
        if self.aind_inj1_type == InjectionType.NANOJECT:
            # Missing protocol_id
            injection_volume = (
                [] if self.aind_inj1_vol is None else [self.aind_inj1_vol]
            )
            return NanojectInjection.model_construct(
                injection_materials=injection_materials,
                recovery_time=self.aind_first_inj_recovery,
                injection_duration=self.aind_inj1_lenghtof_time,
                instrument_id=self.aind_nanoject_number_inj10,
                injection_coordinate_ml=self.aind_virus_m_l,
                injection_coordinate_ap=self.aind_virus_a_p,
                injection_coordinate_depth=self.aind_virus_d_v,
                injection_coordinate_reference=(
                    self.aind_inj1_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj1angle0,
                injection_hemisphere=self.aind_virus_hemisphere,
                injection_volume=injection_volume,
            )
        elif self.aind_inj1_type == InjectionType.IONTOPHORESIS:
            return IontophoresisInjection.model_construct(
                injection_materials=injection_materials,
                recovery_time=self.aind_first_inj_recovery,
                injection_duration=self.aind_inj1_lenghtof_time,
                instrument_id=self.aind_ionto_number_inj1,
                injection_coordinate_ml=self.aind_virus_m_l,
                injection_coordinate_ap=self.aind_virus_a_p,
                injection_coordinate_depth=self.aind_virus_d_v,
                injection_coordinate_reference=(
                    self.aind_inj1_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj1angle0,
                injection_hemisphere=self.aind_virus_hemisphere,
                injection_current=self.aind_inj1_current,
                alternating_current=self.aind_inj1_alternating_time,
            )
        else:
            return BrainInjection.model_construct(
                injection_materials=injection_materials,
                recovery_time=self.aind_first_inj_recovery,
                injection_duration=self.aind_inj1_lenghtof_time,
                injection_coordinate_ml=self.aind_virus_m_l,
                injection_coordinate_ap=self.aind_virus_a_p,
                injection_coordinate_depth=self.aind_virus_d_v,
                injection_coordinate_reference=(
                    self.aind_inj1_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj1angle0,
                injection_hemisphere=self.aind_virus_hemisphere,
            )

    def get_second_injection_procedure(self) -> BrainInjection:
        """Get second injection procedure"""
        injection_materials = (
            []
            if self.aind_inj2_virus_strain_rt is None
            else [
                ViralMaterial.model_construct(
                    name=self.aind_inj2_virus_strain_rt
                )
            ]
        )
        if self.aind_inj2_type == InjectionType.NANOJECT:
            injection_volume = (
                [] if self.aind_inj2_vol is None else [self.aind_inj2_vol]
            )
            return NanojectInjection.model_construct(
                injection_materials=injection_materials,
                recovery_time=self.aind_second_inj_recover,
                injection_duration=self.aind_inj2_lenghtof_time,
                instrument_id=self.aind_nanoject_number_inj2,
                injection_coordinate_ml=self.aind_ml2nd_inj,
                injection_coordinate_ap=self.aind_ap2nd_inj,
                injection_coordinate_depth=self.aind_dv2nd_inj,
                injection_coordinate_reference=(
                    self.aind_inj2_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj2angle0,
                injection_hemisphere=self.aind_hemisphere2nd_inj,
                injection_volume=injection_volume,
            )
        elif self.aind_inj2_type == InjectionType.IONTOPHORESIS:
            return IontophoresisInjection.model_construct(
                injection_materials=injection_materials,
                recovery_time=self.aind_second_inj_recover,
                injection_duration=self.aind_inj2_lenghtof_time,
                instrument_id=self.aind_ionto_number_inj2,
                injection_coordinate_ml=self.aind_ml2nd_inj,
                injection_coordinate_ap=self.aind_ap2nd_inj,
                injection_coordinate_depth=self.aind_dv2nd_inj,
                injection_coordinate_reference=(
                    self.aind_inj2_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj2angle0,
                injection_hemisphere=self.aind_hemisphere2nd_inj,
                injection_current=self.aind_inj2_current,
                alternating_current=self.aind_inj2_alternating_time,
            )
        else:
            return BrainInjection.model_construct(
                injection_materials=injection_materials,
                recovery_time=self.aind_second_inj_recover,
                injection_duration=self.aind_inj2_lenghtof_time,
                injection_coordinate_ml=self.aind_ml2nd_inj,
                injection_coordinate_ap=self.aind_ap2nd_inj,
                injection_coordinate_depth=self.aind_dv2nd_inj,
                injection_coordinate_reference=(
                    self.aind_inj2_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj2angle0,
                injection_hemisphere=self.aind_hemisphere2nd_inj,
            )

    def get_fiber_implant(self):
        """Get a fiber implant procedure"""

        # Need to figure out core_diameter, numerical_aperture, total_length,
        # and targeted_structure
        ophys_probes = []
        if self.aind_fiber_implant1:
            fiber_probe = FiberProbe.model_construct(
                name="Probe A",
                core_diameter=None,
                numerical_aperture=None,
                total_length=None,
            )
            ophys_probes.append(
                OphysProbe.model_construct(
                    fiber_probe=fiber_probe,
                    targeted_structure=None,
                    stereotactic_coordinate_ap=self.aind_virus_a_p,
                    stereotactic_coordinate_ml=self.aind_virus_m_l,
                    stereotactic_coordinate_dv=self.aind_fiber_implant1_dv,
                    stereotactic_coordinate_reference=(
                        self.aind_inj1_coordinates_reference
                    ),
                    bregma_to_lambda_distance=self.aind_breg2_lamb,
                    angle=self.aind_inj1_angle_v2,
                )
            )
        if self.aind_fiber_implant2:
            fiber_probe = FiberProbe.model_construct(
                name="Probe B",
                core_diameter=None,
                numerical_aperture=None,
                total_length=None,
            )
            ophys_probes.append(
                OphysProbe.model_construct(
                    fiber_probe=fiber_probe,
                    targeted_structure=None,
                    stereotactic_coordinate_ap=self.aind_ap2nd_inj,
                    stereotactic_coordinate_ml=self.aind_ml2nd_inj,
                    stereotactic_coordinate_dv=self.aind_fiber_implant2_dv,
                    stereotactic_coordinate_reference=(
                        self.aind_inj2_coordinates_reference
                    ),
                    bregma_to_lambda_distance=self.aind_breg2_lamb,
                    angle=self.aind_inj2_angle_v2,
                )
            )
        # Missing protocol_id
        return FiberImplant.model_construct(
            probes=ophys_probes,
        )

    @property
    def has_injection_procedure(self) -> bool:
        """Return true if injection procedure in nsb list"""
        if self._nsb.procedure is None:
            return False
        else:
            return {
                self._nsb.procedure.INJHPC: True,
                self._nsb.procedure.INJWHC_NP: True,
                self._nsb.procedure.HPINJ: True,
                self._nsb.procedure.HP_INJECTION_OPTIC_FIBER: True,
                self._nsb.procedure.STEREOTAXIC_INJECTION: True,
            }.get(self._nsb.procedure, False)

    @property
    def has_second_injection_procedure(self) -> bool:
        """Return true if second injection in nsb list"""
        if self.aind_inj2_round is not None or self.aind_inj2_type is not None:
            return True
        else:
            return False

    @property
    def has_craniotomy_procedure(self) -> bool:
        """Return true if craniotomy procedure in nsb list"""
        if self._nsb.procedure is None:
            return False
        else:
            return {
                self._nsb.procedure.HPC_CAM: True,
                self._nsb.procedure.HPC_MULTISCOPE: True,
                self._nsb.procedure.HPC_NEUROPIXEL_STYLE: True,
                self._nsb.procedure.INJHPC: True,
                self._nsb.procedure.WHC_NP: True,
                self._nsb.procedure.INJWHC_NP: True,
            }.get(self._nsb.procedure, False)

    @property
    def has_head_frame_procedure(self) -> bool:
        """Return true if headframe procedure in nsb list"""
        if self._nsb.procedure is None:
            return False
        else:
            return {
                self._nsb.procedure.HPC_CAM: True,
                self._nsb.procedure.HPC_MULTISCOPE: True,
                self._nsb.procedure.HPC_NEUROPIXEL_STYLE: True,
                self._nsb.procedure.INJHPC: True,
                self._nsb.procedure.HPINJ: True,
                self._nsb.procedure.HP_TRANSCRANIAL_FOR_ISI: True,
                self._nsb.procedure.HP_ONLY: True,
                self._nsb.procedure.HP_INJECTION_OPTIC_FIBER: True,
            }.get(self._nsb.procedure, False)

    @property
    def has_fiber_implant_procedure(self) -> bool:
        """Return true if fiber implant procedure in nsb list"""
        if self._nsb.procedure is None:
            return False
        else:
            return {
                self._nsb.procedure.HP_INJECTION_OPTIC_FIBER: True,
            }.get(self._nsb.procedure, False)

    def get_basic_surgery(self) -> Surgery:
        """Get a basic surgery if non-procedures info is available."""
        return Surgery.model_construct(
            start_date=self.aind_date_of_surgery,
            experimenter_full_name=self.aind_experimenter_full_name,
            iacuc_protocol=self.aind_iacuc_protocol,
            animal_weight_prior=self.aind_weight_before_surger,
            animal_weight_post=self.aind_weight_after_surgery,
        )

    @property
    def has_unknown_surgery(self) -> bool:
        """Return true if no known procedures are found but data is found"""
        if self._nsb.procedure is None and self.aind_date_of_surgery is None:
            return False
        elif self._nsb.procedure is None:
            return True
        else:
            return not (
                self.has_injection_procedure
                or self.has_fiber_implant_procedure
                or self.has_craniotomy_procedure
                or self.has_head_frame_procedure
            )

    def get_procedure(self) -> List[Surgery]:
        """Return Surgery as best as possible from a record."""

        # Surgery info
        experimenter_full_name = self.aind_experimenter_full_name
        iacuc_protocol = self.aind_iacuc_protocol
        notes = None
        surgeries = []

        # Add craniotomy, headframe, and fiber implant into one surgery
        procedures = []
        if self.has_head_frame_procedure:
            procedures.append(self.get_head_frame_procedure())
        if self.has_craniotomy_procedure:
            procedures.append(self.get_craniotomy_procedure())
        if self.has_fiber_implant_procedure:
            procedures.append(self.get_fiber_implant())
        if (
            self.has_head_frame_procedure
            or self.has_craniotomy_procedure
            or self.has_fiber_implant_procedure
        ):
            start_date = self.aind_date_of_surgery
            animal_weight_prior = self.aind_weight_before_surger
            animal_weight_post = self.aind_weight_after_surgery
            anaesthesia = Anaesthetic.model_construct(
                type=self.aind_anaesthetic_type,
                level=self.aind_hp_iso_level,
            )
            workstation_id = self.aind_hp_work_station
            surgery = Surgery.model_construct(
                start_date=start_date,
                experimenter_full_name=experimenter_full_name,
                iacuc_protocol=iacuc_protocol,
                animal_weight_prior=animal_weight_prior,
                animal_weight_post=animal_weight_post,
                anaesthesia=anaesthesia,
                workstation_id=workstation_id,
                notes=notes,
                procedures=procedures,
            )
            surgeries.append(surgery)

        # create a surgery for 1st injection
        if self.has_injection_procedure:
            start_date = self.aind_date1st_injection
            animal_weight_prior = self.aind_first_injection_weight_be
            animal_weight_post = self.aind_first_injection_weight_af
            anaesthesia = Anaesthetic.model_construct(
                type=self.aind_anaesthetic_type,
                duration=self.aind_first_injection_iso_durat,
                level=self.aind_hp_iso_level,
            )
            workstation_id = self.aind_work_station1st_injection
            surgery = Surgery.model_construct(
                start_date=start_date,
                experimenter_full_name=experimenter_full_name,
                iacuc_protocol=iacuc_protocol,
                animal_weight_prior=animal_weight_prior,
                animal_weight_post=animal_weight_post,
                anaesthesia=anaesthesia,
                workstation_id=workstation_id,
                notes=notes,
                procedures=[self.get_first_injection_procedure()],
            )
            surgeries.append(surgery)

        # create a surgery for 2nd injection
        if (
            self.has_injection_procedure
            and self.has_second_injection_procedure
        ):
            start_date = self.aind_date2nd_injection
            animal_weight_prior = self.aind_second_injection_weight_b
            animal_weight_post = self.aind_second_injection_weight_a
            anaesthesia = Anaesthetic.model_construct(
                type=self.aind_anaesthetic_type,
                duration=self.aind_second_injection_iso_dura,
                level=self.aind_hp_iso_level,
            )
            workstation_id = self.aind_work_station2nd_injection
            surgery = Surgery.model_construct(
                start_date=start_date,
                experimenter_full_name=experimenter_full_name,
                iacuc_protocol=iacuc_protocol,
                animal_weight_prior=animal_weight_prior,
                animal_weight_post=animal_weight_post,
                anaesthesia=anaesthesia,
                workstation_id=workstation_id,
                notes=notes,
                procedures=[self.get_second_injection_procedure()],
            )
            surgeries.append(surgery)
        if self.has_unknown_surgery:
            surgeries.append(self.get_basic_surgery())
        return surgeries
