use super::*;

pub(super) fn maybe_install_auto_load_hook() {
    let save_stem = match env::var("RRT_AUTO_LOAD_SAVE") {
        Ok(value) if !value.trim().is_empty() => value,
        _ => return,
    };
    let _ = AUTO_LOAD_SAVE_STEM.set(save_stem);
    if AUTO_LOAD_HOOK_INSTALLED.swap(true, Ordering::AcqRel) {
        return;
    }

    append_log_message(AUTO_LOAD_STARTED_MESSAGE);
    AUTO_LOAD_THREAD_STARTED.store(true, Ordering::Release);
    if unsafe { install_shell_state_service_hook() } {
        append_log_message(AUTO_LOAD_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_profile_startup_dispatch_hook() } {
        append_log_message(AUTO_LOAD_PROFILE_DISPATCH_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_runtime_reset_hook() } {
        append_log_message(AUTO_LOAD_RUNTIME_RESET_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_allocator_hook() } {
        append_log_message(AUTO_LOAD_ALLOCATOR_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_load_screen_scalar_hook() } {
        append_log_message(AUTO_LOAD_LOAD_SCREEN_SCALAR_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_load_screen_construct_hook() } {
        append_log_message(AUTO_LOAD_LOAD_SCREEN_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_load_screen_message_hook() } {
        append_log_message(AUTO_LOAD_LOAD_SCREEN_MESSAGE_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_runtime_prime_hook() } {
        append_log_message(AUTO_LOAD_RUNTIME_PRIME_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_frame_cycle_hook() } {
        append_log_message(AUTO_LOAD_FRAME_CYCLE_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_object_service_hook() } {
        append_log_message(AUTO_LOAD_OBJECT_SERVICE_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_child_service_hook() } {
        append_log_message(AUTO_LOAD_CHILD_SERVICE_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_shell_publish_hook() } {
        append_log_message(AUTO_LOAD_PUBLISH_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_shell_unpublish_hook() } {
        append_log_message(AUTO_LOAD_UNPUBLISH_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_shell_object_teardown_hook() } {
        append_log_message(AUTO_LOAD_OBJECT_TEARDOWN_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_shell_object_range_remove_hook() } {
        append_log_message(AUTO_LOAD_OBJECT_RANGE_REMOVE_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_shell_remove_node_hook() } {
        append_log_message(AUTO_LOAD_REMOVE_NODE_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_shell_node_vcall_hook() } {
        append_log_message(AUTO_LOAD_NODE_VCALL_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    if unsafe { install_mode2_teardown_hook() } {
        append_log_message(AUTO_LOAD_MODE2_TEARDOWN_HOOK_INSTALLED_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
}

pub(super) fn run_auto_load_worker() {
    append_log_message(AUTO_LOAD_CALLING_MESSAGE);
    let staged = unsafe { invoke_manual_load_branch() };
    if staged {
        append_log_message(AUTO_LOAD_TRIGGERED_MESSAGE);
        append_log_message(AUTO_LOAD_SUCCESS_MESSAGE);
    } else {
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
    }
    AUTO_LOAD_IN_PROGRESS.store(false, Ordering::Release);
}

pub(super) unsafe fn invoke_manual_load_branch() -> bool {
    let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
    if shell_state.is_null() {
        return false;
    }

    let shell_transition_mode: ShellTransitionModeFn =
        unsafe { mem::transmute(SHELL_TRANSITION_MODE_ADDR) };

    AUTO_LOAD_ALLOCATOR_WINDOW_LOG_COUNT.store(0, Ordering::Release);
    AUTO_LOAD_ALLOCATOR_WINDOW_ACTIVE.store(true, Ordering::Release);
    append_log_message(AUTO_LOAD_OWNER_ENTRY_MESSAGE);
    let _ = unsafe { shell_transition_mode(shell_state, SHELL_MODE_STARTUP_LOAD_DISPATCH, 0) };
    AUTO_LOAD_ALLOCATOR_WINDOW_ACTIVE.store(false, Ordering::Release);
    append_log_message(AUTO_LOAD_OWNER_RETURNED_MESSAGE);
    log_post_transition_state();

    true
}

pub(super) unsafe fn stage_manual_load_request(save_stem: &str) -> bool {
    if save_stem.is_empty() || save_stem.as_bytes().contains(&0) {
        return false;
    }

    let runtime_profile = unsafe { read_ptr(RUNTIME_PROFILE_PTR_ADDR as *const u8) };
    if runtime_profile.is_null() {
        return false;
    }

    let path_seed = unsafe { runtime_profile.add(RUNTIME_PROFILE_MANUAL_LOAD_PATH_OFFSET) };
    if unsafe { write_c_string(path_seed, 260, save_stem.as_bytes()) }.is_none() {
        return false;
    }

    unsafe {
        ptr::write_unaligned(
            runtime_profile
                .add(RUNTIME_PROFILE_STARTUP_SELECTOR_OFFSET)
                .cast::<u8>(),
            STARTUP_SELECTOR_SCENARIO_LOAD,
        )
    };
    unsafe {
        ptr::write_unaligned(
            runtime_profile
                .add(RUNTIME_PROFILE_PENDING_LOAD_BYTE_OFFSET)
                .cast::<u8>(),
            0,
        )
    };
    log_auto_load_launch_state(runtime_profile);
    true
}

pub(super) unsafe fn write_c_string(
    destination: *mut u8,
    capacity: usize,
    bytes: &[u8],
) -> Option<()> {
    if bytes.len() + 1 > capacity {
        return None;
    }

    unsafe { ptr::write_bytes(destination, 0, capacity) };
    unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), destination, bytes.len()) };
    Some(())
}

pub(super) unsafe fn runtime_saved_world_restore_gate_mask() -> u32 {
    let mut mask = 0_u32;
    let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
    if !shell_state.is_null() {
        mask |= 0x1;
    }
    let shell_controller = unsafe { read_ptr(SHELL_CONTROLLER_PTR_ADDR as *const u8) };
    if !shell_controller.is_null() {
        mask |= 0x2;
    }
    let active_mode = unsafe { resolve_active_mode_ptr() };
    if !active_mode.is_null() {
        mask |= 0x4;
    }
    mask
}

pub(super) unsafe fn current_mode_id() -> u32 {
    let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
    if shell_state.is_null() {
        return 0;
    }
    unsafe { read_u32(shell_state.add(SHELL_STATE_ACTIVE_MODE_OFFSET)) }
}

pub(super) fn auto_load_ready_polls() -> u32 {
    env::var("RRT_AUTO_LOAD_READY_POLLS")
        .ok()
        .and_then(|value| value.parse::<u32>().ok())
        .filter(|value| *value > 0)
        .unwrap_or(AUTO_LOAD_READY_POLLS)
}

pub(super) fn auto_load_defer_polls() -> u32 {
    env::var("RRT_AUTO_LOAD_DEFER_POLLS")
        .ok()
        .and_then(|value| value.parse::<u32>().ok())
        .unwrap_or(AUTO_LOAD_DEFER_POLLS)
}

pub(super) fn maybe_service_auto_load_on_main_thread() {
    if !AUTO_LOAD_HOOK_INSTALLED.load(Ordering::Acquire)
        || AUTO_LOAD_ATTEMPTED.load(Ordering::Acquire)
        || AUTO_LOAD_IN_PROGRESS.load(Ordering::Acquire)
    {
        return;
    }

    let gate_mask = unsafe { runtime_saved_world_restore_gate_mask() };
    let last_gate_mask = AUTO_LOAD_LAST_GATE_MASK.swap(gate_mask, Ordering::AcqRel);
    if gate_mask != last_gate_mask {
        log_auto_load_gate_mask(gate_mask);
    }

    let mode_id = unsafe { current_mode_id() };
    let ready = gate_mask == 0x7 && mode_id == 2;
    let ready_count = if ready {
        AUTO_LOAD_READY_COUNT.fetch_add(1, Ordering::AcqRel) + 1
    } else {
        AUTO_LOAD_READY_COUNT.store(0, Ordering::Release);
        AUTO_LOAD_DEFERRED.store(false, Ordering::Release);
        0
    };
    if ready {
        append_log_message(AUTO_LOAD_READY_COUNT_MESSAGE);
        log_auto_load_ready_count(ready_count, gate_mask, mode_id);
    }

    let ready_polls = auto_load_ready_polls();
    if ready_count < ready_polls {
        return;
    }

    if !AUTO_LOAD_DEFERRED.load(Ordering::Acquire) {
        AUTO_LOAD_DEFERRED.store(true, Ordering::Release);
        append_log_message(AUTO_LOAD_READY_MESSAGE);
        append_log_message(AUTO_LOAD_DEFERRED_MESSAGE);
        return;
    }

    if ready_count < ready_polls.saturating_add(auto_load_defer_polls()) {
        return;
    }

    if AUTO_LOAD_ATTEMPTED.swap(true, Ordering::AcqRel) {
        return;
    }

    if !AUTO_LOAD_TRANSITION_ARMED.load(Ordering::Acquire) {
        let Some(save_stem) = AUTO_LOAD_SAVE_STEM.get() else {
            append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
            AUTO_LOAD_ATTEMPTED.store(true, Ordering::Release);
            return;
        };
        if unsafe { stage_manual_load_request(save_stem) } {
            AUTO_LOAD_TRANSITION_ARMED.store(true, Ordering::Release);
            AUTO_LOAD_ARMED_TICK_COUNT.store(0, Ordering::Release);
            append_log_message(AUTO_LOAD_STAGED_MESSAGE);
            log_auto_load_armed_tick();
            AUTO_LOAD_ATTEMPTED.store(true, Ordering::Release);
            AUTO_LOAD_IN_PROGRESS.store(true, Ordering::Release);
            append_log_message(AUTO_LOAD_ARMED_TICK_MESSAGE);
            append_log_message(AUTO_LOAD_READY_MESSAGE);
            run_auto_load_worker();
            return;
        }
        append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
        AUTO_LOAD_ATTEMPTED.store(true, Ordering::Release);
        return;
    }

    AUTO_LOAD_IN_PROGRESS.store(true, Ordering::Release);
    append_log_message(AUTO_LOAD_READY_MESSAGE);
    run_auto_load_worker();
}

pub(super) fn log_auto_load_gate_mask(mask: u32) {
    let mut line = String::from("rrt-hook: auto load gate mask ");
    let global_active_mode = unsafe { read_ptr(ACTIVE_MODE_PTR_ADDR as *const u8) } as usize;
    let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
    let mode_id = if shell_state.is_null() {
        0
    } else {
        unsafe { read_u32(shell_state.add(SHELL_STATE_ACTIVE_MODE_OFFSET)) as usize }
    };
    let field_active_mode_object = if shell_state.is_null() {
        0
    } else {
        unsafe { read_ptr(shell_state.add(SHELL_STATE_ACTIVE_MODE_OBJECT_OFFSET)) as usize }
    };
    let startup_selector = unsafe {
        let runtime_profile = read_ptr(RUNTIME_PROFILE_PTR_ADDR as *const u8);
        if runtime_profile.is_null() {
            0
        } else {
            read_u8(runtime_profile.add(RUNTIME_PROFILE_STARTUP_SELECTOR_OFFSET)) as usize
        }
    };
    let _ = write!(
        &mut line,
        "0x{mask:01x} shell_state={} shell_controller={} active_mode={} global_active_mode=0x{global_active_mode:08x} mode_id=0x{mode_id:08x} startup_selector=0x{startup_selector:08x} field_active_mode_object=0x{field_active_mode_object:08x}\n",
        (mask & 0x1) != 0,
        (mask & 0x2) != 0,
        (mask & 0x4) != 0,
    );
    append_log_line(&line);
}

pub(super) fn log_auto_load_armed_tick() {
    let tick_count = AUTO_LOAD_ARMED_TICK_COUNT.fetch_add(1, Ordering::AcqRel) + 1;
    let gate_mask = unsafe { runtime_saved_world_restore_gate_mask() };
    let global_active_mode = unsafe { read_ptr(ACTIVE_MODE_PTR_ADDR as *const u8) } as usize;
    let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
    let mode_id = if shell_state.is_null() {
        0
    } else {
        unsafe { read_u32(shell_state.add(SHELL_STATE_ACTIVE_MODE_OFFSET)) as usize }
    };
    let startup_selector = unsafe {
        let runtime_profile = read_ptr(RUNTIME_PROFILE_PTR_ADDR as *const u8);
        if runtime_profile.is_null() {
            0
        } else {
            read_u8(runtime_profile.add(RUNTIME_PROFILE_STARTUP_SELECTOR_OFFSET)) as usize
        }
    };
    let mut line = String::from("rrt-hook: auto load armed tick ");
    let _ = write!(
        &mut line,
        "count=0x{tick_count:08x} gate_mask=0x{gate_mask:01x} mode_id=0x{mode_id:08x} startup_selector=0x{startup_selector:08x} global_active_mode=0x{global_active_mode:08x}\n",
    );
    append_log_line(&line);
}

pub(super) fn log_auto_load_ready_count(count: u32, gate_mask: u32, mode_id: u32) {
    let startup_selector = unsafe {
        let runtime_profile = read_ptr(RUNTIME_PROFILE_PTR_ADDR as *const u8);
        if runtime_profile.is_null() {
            0
        } else {
            read_u8(runtime_profile.add(RUNTIME_PROFILE_STARTUP_SELECTOR_OFFSET)) as usize
        }
    };
    let mut line = String::from("rrt-hook: auto load ready count ");
    let _ = write!(
        &mut line,
        "count=0x{count:08x} gate_mask=0x{gate_mask:01x} mode_id=0x{mode_id:08x} startup_selector=0x{startup_selector:08x}\n",
    );
    append_log_line(&line);
}
