from __future__ import annotations

from typing import List, Optional, Dict, Any, TYPE_CHECKING
from transitions import Machine

if TYPE_CHECKING:
    from .building import Building
    from .passenger import Passenger
    from .floor import Floor


class Elevator:
    states = ["IDLE", "MOVING_UP", "MOVING_DOWN"]

    def __init__(self, elevator_id: str, building: Building, capacity: int) -> None:
        self.elevator_id: str = elevator_id
        self.building: Building = building
        self.capacity: int = capacity
        self.current_floor: Floor = building.floors[0] # Start at floor 0
        self.target_floor: Optional[Floor] = None
        self.onboard_passengers: List[Passenger] = []

        self.machine = Machine(model=self, states=Elevator.states, initial="IDLE")
        self.machine.add_transition("_move_up", ["MOVING_UP", "IDLE"], "MOVING_UP")
        self.machine.add_transition("_move_down", ["MOVING_DOWN", "IDLE"], "MOVING_DOWN")
        self.machine.add_transition("_stop", ["MOVING_UP", "MOVING_DOWN"], "IDLE")


    def __repr__(self) -> str:
        return f"Elevator(id={self.elevator_id}, current_floor={self.current_floor.floor_id}, target_floor={self.target_floor.floor_id if self.target_floor else None}, state={self.state}, passengers={len(self.onboard_passengers)}/{self.capacity})"

    def to_dict(self) -> Dict[str, Any]:
        return {
            'type': 'Elevator',
            'id': self.elevator_id,
            'capacity': self.capacity,
            'current_floor': self.current_floor.floor_id,
            'target_floor': self.target_floor.floor_id if self.target_floor else None,
            'state': self.state,
            'onboard_passengers_count': len(self.onboard_passengers),
            'onboard_passengers': [p.to_dict() for p in self.onboard_passengers]
        }

    def stop(self) -> None:
        self._stop()

    def move_up(self) -> None:
        if self.current_floor >= self.target_floor:
            raise ValueError("Target floor must be above current floor to move up.")
        self._move_up()

    def move_down(self) -> None:
        if self.current_floor <= self.target_floor:
            raise ValueError("Target floor must be below current floor to move down.")
        self._move_down()

    def move(self) -> None:
        if self.current_floor < self.target_floor:
            self.move_up()
        elif self.current_floor > self.target_floor:
            self.move_down()
        else:
            raise ValueError("Current floor is the same as target floor; cannot move.")

    def is_at_capacity(self) -> bool:
        return len(self.onboard_passengers) >= self.capacity

    def available_capacity(self) -> int:
        return max(0, self.capacity - len(self.onboard_passengers))

    def embark_passengers(self, current_tick: int) -> None:
        for passenger in list(self.current_floor.waiting_passengers):
            if len(self.onboard_passengers) >= self.capacity:
                break
            passenger.embark(current_tick)
            self.onboard_passengers.append(passenger)
            self.current_floor.remove_waiting_passenger(passenger)

    def disembark_passengers(self, current_tick: int) -> None:
        for passenger in list(self.onboard_passengers):
            if passenger.destination_floor != self.current_floor:
                continue
            passenger.disembark(current_tick)
            self.onboard_passengers.remove(passenger)
            self.current_floor.add_dismbarked_passenger(passenger)

    def embark_waiting_passengers(self, current_tick: int) -> None:
        if self.state == "IDLE" and not self.is_at_capacity() and self.current_floor.waiting_passengers:
            self.embark_passengers(current_tick)

    def disembark_arrived_passengers(self, current_tick: int) -> None:
        self.disembark_passengers(current_tick)

    def tick_movement(self) -> None:
        if not self.target_floor:
            #? No target floor set, stay idle
            if self.state != "IDLE":
                self.stop()
        elif self.state == "IDLE":
            #? Start moving towards target if not already there
            if self.current_floor != self.target_floor:
                self.move()
        elif self.state == "MOVING_UP":
            #? Actually move up one floor
            current_floor_num = int(self.current_floor)
            self.current_floor = self.building.floors[current_floor_num + 1]
            if self.current_floor == self.target_floor:
                self.stop()
        elif self.state == "MOVING_DOWN":
            #? Actually move down one floor
            current_floor_num = int(self.current_floor)
            self.current_floor = self.building.floors[current_floor_num - 1]
            if self.current_floor == self.target_floor:
                self.stop()
