"""
This was initially generated by datamodel-codegen from the labware schema in
shared-data. It's been modified by hand to be more friendly.

TODO: 20210330 Amit - consider moving this to opentrons-shared-data.
"""

from __future__ import annotations

from enum import Enum
from typing import Dict, List, Optional

from pydantic import BaseModel, Extra, Field
from typing_extensions import Literal

SAFE_STRING_REGEX = "^[a-z0-9._]+$"


class CornerOffsetFromSlot(BaseModel):
    """
    Distance from left-front-bottom corner of slot to left-front-bottom corner
     of labware bounding box. Used for labware that spans multiple slots. For
      labware that does not span multiple slots, x/y/z should all be zero.
    """

    x: float
    y: float
    z: float


class BrandData(BaseModel):
    brand: str = Field(..., description="Brand/manufacturer name")
    brandId: Optional[List[str]] = Field(
        None,
        description="An array of manufacture numbers pertaining to a given labware",
    )
    links: Optional[List[str]] = Field(
        None, description="URLs for manufacturer page(s)"
    )


class DisplayCategory(str, Enum):
    tipRack = "tipRack"
    tubeRack = "tubeRack"
    reservoir = "reservoir"
    trash = "trash"
    wellPlate = "wellPlate"
    aluminumBlock = "aluminumBlock"
    other = "other"


class Metadata(BaseModel):
    """
    Properties used for search and display
    """

    displayName: str = Field(..., description="Easy to remember name of labware")
    displayCategory: DisplayCategory = Field(
        ..., description="Label(s) used in UI to categorize labware"
    )
    displayVolumeUnits: Literal["µL", "mL", "L"] = Field(
        ..., description="Volume units for display"
    )
    tags: Optional[List[str]] = Field(
        None, description="List of descriptions for a given labware"
    )


class Parameters(BaseModel):
    """
    Internal describers used to determine pipette movement to labware
    """

    format: Literal[
        "96Standard", "384Standard", "trough", "irregular", "trash"
    ] = Field(
        ..., description="Property to determine compatibility with multichannel pipette"
    )
    quirks: Optional[List[str]] = Field(
        None,
        description="Property to classify a specific behavior this labware "
        "should have",
    )
    isTiprack: bool = Field(
        ..., description="Flag marking whether a labware is a tiprack or not"
    )
    tipLength: Optional[float] = Field(
        None,
        ge=0.0,
        description="Required if labware is tiprack, specifies length of tip"
        " from drawing or as measured with calipers",
    )
    tipOverlap: Optional[float] = Field(
        None,
        ge=0.0,
        description="Required if labware is tiprack, specifies the length of "
        "the area of the tip that overlaps the nozzle of the pipette",
    )
    loadName: str = Field(
        ...,
        description="Name used to reference a labware definition",
        regex=SAFE_STRING_REGEX,
    )
    isMagneticModuleCompatible: bool = Field(
        ...,
        description="Flag marking whether a labware is compatible by default "
        "with the Magnetic Module",
    )
    magneticModuleEngageHeight: Optional[float] = Field(
        None, ge=0.0, description="Distance to move magnetic module magnets to engage"
    )


class Dimensions(BaseModel):
    """
    Outer dimensions of a labware
    """

    yDimension: float = Field(..., ge=0.0)
    zDimension: float = Field(..., ge=0.0)
    xDimension: float = Field(..., ge=0.0)


class WellDefinition(BaseModel):
    class Config:
        extra = Extra.allow

    depth: float = Field(..., ge=0.0)
    x: float = Field(
        ...,
        ge=0.0,
        description="x location of center-bottom of well in reference to "
        "left-front-bottom of labware",
    )
    y: float = Field(
        ...,
        ge=0.0,
        description="y location of center-bottom of well in reference to "
        "left-front-bottom of labware",
    )
    z: float = Field(
        ...,
        ge=0.0,
        description="z location of center-bottom of well in reference to "
        "left-front-bottom of labware",
    )
    totalLiquidVolume: float = Field(
        ..., ge=0.0, description="Total well, tube, or tip volume in microliters"
    )
    xDimension: Optional[float] = Field(
        None, ge=0.0, description="x dimension of rectangular wells"
    )
    yDimension: Optional[float] = Field(
        None, ge=0.0, description="y dimension of rectangular wells"
    )
    diameter: Optional[float] = Field(
        None, ge=0.0, description="diameter of circular wells"
    )
    shape: Literal["rectangular", "circular"] = Field(
        ...,
        description="If 'rectangular', use xDimension and "
        "yDimension; if 'circular' use diameter",
    )


class Metadata1(BaseModel):
    """
    Metadata specific to a grid of wells in a labware
    """

    displayName: Optional[str] = Field(
        None, description="User-readable name for the well group"
    )
    displayCategory: Optional[DisplayCategory] = Field(
        None, description="Label(s) used in UI to categorize well groups"
    )
    wellBottomShape: Optional[Literal["flat", "u", "v"]] = Field(
        None, description="Bottom shape of the well for UI purposes"
    )


class Group(BaseModel):
    wells: List[str] = Field(
        ..., description="An array of wells that contain the same metadata", min_items=1
    )
    metadata: Metadata1 = Field(
        ..., description="Metadata specific to a grid of wells in a labware"
    )
    brand: Optional[BrandData] = Field(
        None, description="Brand data for the well group (e.g. for tubes)"
    )


class LabwareDefinition(BaseModel):
    schemaVersion: Literal[1, 2] = Field(
        ..., description="Which schema version a labware is using"
    )
    version: int = Field(
        ...,
        description="Version of the labware definition itself "
        "(eg myPlate v1/v2/v3). An incrementing integer",
        ge=1.0,
    )
    namespace: str = Field(..., regex=SAFE_STRING_REGEX)
    metadata: Metadata = Field(
        ..., description="Properties used for search and display"
    )
    brand: BrandData = Field(
        ...,
        description="Real-world labware that the definition is modeled "
        "from and/or compatible with",
    )
    parameters: Parameters = Field(
        ...,
        description="Internal describers used to determine pipette movement "
        "to labware",
    )
    ordering: List[List[str]] = Field(
        ...,
        description="Generated array that keeps track of how wells should be "
        "ordered in a labware",
    )
    cornerOffsetFromSlot: CornerOffsetFromSlot = Field(
        ...,
        description="Distance from left-front-bottom corner of slot to "
        "left-front-bottom corner of labware bounding box. Used for "
        "labware that spans multiple slots. For labware that does "
        "not span multiple slots, x/y/z should all be zero.",
    )
    dimensions: Dimensions = Field(..., description="Outer dimensions of a labware")
    wells: Dict[str, WellDefinition] = Field(
        ...,
        description="Unordered object of well objects with position and "
        "dimensional information",
    )
    groups: List[Group] = Field(
        ...,
        description="Logical well groupings for metadata/display purposes; "
        "changes in groups do not affect protocol execution",
    )
