use crate::calendar::BoundaryEventKind;
use crate::engine::command::{BoundaryEvent, ServiceEvent};
use crate::engine::service::service_periodic_boundary;
use crate::state::{CalendarPoint, RuntimeState};

pub(super) fn advance_to_target_calendar_point(
    state: &mut RuntimeState,
    target: crate::CalendarPoint,
    boundary_events: &mut Vec<BoundaryEvent>,
    service_events: &mut Vec<ServiceEvent>,
) -> Result<u64, String> {
    target.validate()?;
    if target < state.calendar {
        return Err(format!(
            "advance_to target {:?} is earlier than current calendar {:?}",
            target, state.calendar
        ));
    }

    let mut steps = 0_u64;
    while state.calendar < target {
        step_once(state, boundary_events, service_events)?;
        steps += 1;
    }
    Ok(steps)
}

pub(super) fn step_count(
    state: &mut RuntimeState,
    steps: u32,
    boundary_events: &mut Vec<BoundaryEvent>,
    service_events: &mut Vec<ServiceEvent>,
) -> Result<u64, String> {
    for _ in 0..steps {
        step_once(state, boundary_events, service_events)?;
    }
    Ok(steps.into())
}

pub(super) fn step_once(
    state: &mut RuntimeState,
    boundary_events: &mut Vec<BoundaryEvent>,
    service_events: &mut Vec<ServiceEvent>,
) -> Result<(), String> {
    let prior_calendar = state.calendar;
    let boundary = state.calendar.step_forward();
    if boundary != BoundaryEventKind::Tick {
        boundary_events.push(BoundaryEvent {
            kind: boundary_kind_label(boundary).to_string(),
            calendar: state.calendar,
        });
    }
    if boundary == BoundaryEventKind::YearRollover {
        service_sync_world_restore_time_from_calendar(state, prior_calendar);
        service_periodic_boundary(state, service_events)?;
    }
    service_sync_world_restore_time_from_calendar(state, state.calendar);
    Ok(())
}

pub(super) fn boundary_kind_label(boundary: BoundaryEventKind) -> &'static str {
    match boundary {
        BoundaryEventKind::Tick => "tick",
        BoundaryEventKind::PhaseRollover => "phase_rollover",
        BoundaryEventKind::MonthRollover => "month_rollover",
        BoundaryEventKind::YearRollover => "year_rollover",
    }
}

pub(super) fn service_sync_world_restore_time_from_calendar(
    state: &mut RuntimeState,
    calendar: CalendarPoint,
) {
    if let Ok(year_word) = u16::try_from(calendar.year) {
        state.world_restore.packed_year_word_raw_u16 = Some(year_word);
    }
    if let Ok(partial_year_progress) = u8::try_from(calendar.month_slot.saturating_add(1)) {
        state.world_restore.partial_year_progress_raw_u8 = Some(partial_year_progress);
    }
    if let Some(tuple) =
        crate::derived::runtime_derive_packed_calendar_tuple_from_calendar_point(calendar)
    {
        let (word_0, word_1) = crate::derived::runtime_encode_packed_calendar_tuple(tuple);
        state.world_restore.current_calendar_tuple_word_raw_u32 = Some(word_0);
        state.world_restore.current_calendar_tuple_word_2_raw_u32 = Some(word_1);
        if let Some(absolute_counter) =
            crate::derived::runtime_pack_packed_calendar_tuple_to_absolute_counter(tuple)
        {
            state.world_restore.absolute_counter_raw_u32 = Some(absolute_counter);
            state.world_restore.absolute_counter_mirror_raw_u32 = Some(absolute_counter);
        }
    }
}
