from .helpers import stringify_location
from . import types as command_types

from typing import Union, Sequence, Optional, TYPE_CHECKING

from opentrons.protocols.api_support.util import FlowRates

if TYPE_CHECKING:
    from opentrons.protocol_api.instrument_context import InstrumentContext
    from opentrons.protocol_api.labware import Well
    from opentrons.types import Location


Apiv2Locations = Sequence[Union["Location", "Well"]]
Apiv2Instruments = Sequence["InstrumentContext"]


def combine_locations(location: Sequence) -> str:
    if len(location) > 1:
        loc1 = stringify_location(location[0])
        loc2 = stringify_location(location[1])
        return f"{loc1} and {loc2}"
    elif len(location) == 1:
        loc1 = stringify_location(location[0])
        return f"{loc1}"
    else:
        return ""


def paired_aspirate(
    instruments: Apiv2Instruments,
    volume: float,
    locations: Apiv2Locations,
    rate: float,
    pub_type: str,
) -> command_types.AspirateCommand:
    loc_text = combine_locations(locations)
    flow_rate = min(
        rate * FlowRates(instr._implementation).aspirate for instr in instruments
    )
    text_type = f"{pub_type}: Aspirating "
    text_content = f"{volume} uL from {loc_text} at {flow_rate} uL/sec"
    text = text_type + text_content
    return {
        "name": command_types.ASPIRATE,
        "payload": {
            "instruments": instruments,
            "volume": volume,
            "locations": locations,
            "rate": rate,
            "text": text,
        },
    }


def paired_dispense(
    instruments: Apiv2Instruments,
    volume: float,
    locations: Apiv2Locations,
    rate: float,
    pub_type: str,
) -> command_types.DispenseCommand:
    loc_text = combine_locations(locations)
    flow_rate = min(
        rate * FlowRates(instr._implementation).dispense for instr in instruments
    )
    text_type = f"{pub_type}: Dispensing "
    text_content = f"{volume} uL into {loc_text} at {flow_rate} uL/sec"
    text = text_type + text_content
    return {
        "name": command_types.DISPENSE,
        "payload": {
            "instruments": instruments,
            "volume": volume,
            "locations": locations,
            "rate": rate,
            "text": text,
        },
    }


def paired_mix(
    instruments: Apiv2Instruments,
    locations: Optional[Apiv2Locations],
    repetitions: int,
    volume: float,
    pub_type: str,
) -> command_types.MixCommand:
    text_type = f"{pub_type}: Mixing "
    text_content = "{repetitions} times with a volume of {volume} ul"
    text = text_type + text_content
    return {
        "name": command_types.MIX,
        "payload": {
            "instruments": instruments,
            "locations": locations,
            "volume": volume,
            "repetitions": repetitions,
            "text": text,
        },
    }


def paired_blow_out(
    instruments: Apiv2Instruments, locations: Optional[Apiv2Locations], pub_type: str
) -> command_types.BlowOutCommand:
    text = f"{pub_type}: Blowing out"

    if locations is not None:
        location_text = combine_locations(locations)
        text += f" at {location_text}"

    return {
        "name": command_types.BLOW_OUT,
        "payload": {"instruments": instruments, "locations": locations, "text": text},
    }


def paired_touch_tip(
    instruments: Apiv2Instruments, locations: Optional[Apiv2Locations], pub_type: str
) -> command_types.TouchTipCommand:
    text = f"{pub_type}: Touching tip"

    if locations is not None:
        location_text = combine_locations(locations)
        text += f" at {location_text}"
    return {
        "name": command_types.TOUCH_TIP,
        "payload": {"instruments": instruments, "locations": locations, "text": text},
    }


def air_gap() -> command_types.AirGapCommand:
    text = "Air gap"
    return {"name": command_types.AIR_GAP, "payload": {"text": text}}


def return_tip() -> command_types.ReturnTipCommand:
    text = "Returning tip"
    return {"name": command_types.RETURN_TIP, "payload": {"text": text}}


def paired_pick_up_tip(
    instruments: Apiv2Instruments, locations: Apiv2Locations, pub_type: str
) -> command_types.PickUpTipCommand:
    location_text = combine_locations(locations)
    text = f"{pub_type}: Picking up tip from {location_text}"
    return {
        "name": command_types.PICK_UP_TIP,
        "payload": {"instruments": instruments, "locations": locations, "text": text},
    }


def paired_drop_tip(
    instruments: Apiv2Instruments, locations: Apiv2Locations, pub_type: str
) -> command_types.DropTipCommand:
    location_text = combine_locations(locations)
    text = f"{pub_type}: Dropping tip into {location_text}"
    return {
        "name": command_types.DROP_TIP,
        "payload": {"instruments": instruments, "locations": locations, "text": text},
    }


def paired_move_to(
    instruments: Apiv2Instruments, locations: Apiv2Locations, pub_type: str
) -> command_types.MoveToCommand:
    location_text = combine_locations(locations)
    text = f"{pub_type}: Moving to {location_text}"
    return {
        "name": command_types.MOVE_TO,
        "payload": {"instruments": instruments, "locations": locations, "text": text},
    }
