"""
Cầu Nối IPC Đọc Telemetry từ AutoPOE2 Core Engine qua Shared Memory
(Windows Seqlock Double-Buffer + Low HP Passive Alert)
"""

import mmap
import struct
import threading
import time
from dataclasses import dataclass, field
from enum import IntEnum
from typing import Callable, List, Optional

SHM_NAME = "Local\\POE2_Auto_SharedMem_v1"
SHM_SIZE = 4 * 1024 * 1024
SHARED_MEM_MAGIC = 0x504F45324155544F  # "POE2AUTO"
PROTOCOL_VERSION = 0x00050005          # v0.5.5

class MacroOpCode(IntEnum):
    IDLE = 0
    NAVIGATE_TO_COORD = 1
    ENGAGE_TARGET = 2
    PICKUP_ITEM = 3
    CAST_EMERGENCY_PORTAL = 4
    EXECUTE_STASH_ROUTINE = 5
    EXECUTE_VENDOR_ROUTINE = 6
    EXECUTE_MAP_DEVICE_ROUTINE = 7
    EXECUTE_NPC_INTERACT_ROUTINE = 8
    SELECT_ATLAS_NODE = 9
    VERIFY_MAP_DEVICE_UI = 10
    FORCE_PORTAL_RETURN = 11
    SET_COMBAT_PROFILE = 12
    SET_MAP_SCENARIO = 95
    TRIGGER_WORLD_MAP_TRAVEL = 96
    TRIGGER_AUTO_LOGIN = 97
    RECALIBRATE_XYZ = 98
    PANIC_STOP = 99

class CombatProfile(IntEnum):
    """Hồ sơ chiến thuật giao chiến (Combat Profile) tương thích C++ protocol.hpp."""
    MOB_CLEARING = 0     # Dọn dẹp quái thường / Pack AoE Clear (Mặc định)
    BOSS_BURST = 1       # Dồn sát thương Boss / Single-target Stagger Burst
    SURVIVAL_KITING = 2  # Phòng thủ né đòn / Kiting / ES Recovery

class CharacterLifecycleState(IntEnum):
    """Máy trạng thái quản lý vòng đời nhân vật chuẩn Doc 58 (BotLifecycleFSM)."""
    UNINITIALIZED_OR_LOADING = 0  # Khởi tạo, chờ nạp game / Loading Screen
    HIDEOUT_IDLE = 1              # Đứng yên tuyệt đối tại Hideout, chờ lệnh
    HIDEOUT_STASHING = 2          # Đang mở hòm đồ Stash, cất giữ currency & loot
    HIDEOUT_PREPARE_MAP = 3       # Đang kiểm tra/chế tác Waystone & Precursor Tablet
    HIDEOUT_MAP_DEVICE_APPROACH = 4 # Tiếp cận Map Device theo tọa độ thực thể RAM
    HIDEOUT_MAP_DEVICE_ACTIVATE = 5 # Mở UI Map Device, chọn Node Atlas, socket & bật Map
    HIDEOUT_ENTER_PORTAL = 6      # Định vị portal thực thể và click bước vào Map
    MAP_LOADING_GRACE = 7         # Màn hình tải Map xong, bảo vệ Grace Period bất tử
    MAP_FARMING_COMBAT = 8        # Chạy map, diệt quái, nhặt đồ, né đòn, dọn boss
    MAP_COMPLETED_PORTAL_OUT = 9  # Hoàn thành mục tiêu map, mở portal về Hideout
    EMERGENCY_RECOVERY = 10       # Xử lý tử trận (Respawn), mất kết nối (AutoLogin)

@dataclass
class PlayerTelemetry:
    player_addr: int = 0
    current_hp: int = 0
    max_hp: int = 0
    current_mana: int = 0
    max_mana: int = 0
    current_es: int = 0
    max_es: int = 0
    current_ward: int = 0
    max_ward: int = 0
    current_spirit: int = 0
    max_spirit: int = 0
    hp_percentage: float = 100.0
    pos_x: float = 0.0
    pos_y: float = 0.0
    pos_z: float = 0.0
    active_weapon_set: int = 1
    movement_flags: int = 0
    debuff_mask: int = 0

@dataclass
class CoreTelemetrySnapshot:
    is_online: bool = False
    snapshot_id: int = 0
    timestamp_ms: int = 0
    player: PlayerTelemetry = field(default_factory=PlayerTelemetry)
    entity_count: int = 0
    monsters_count: int = 0
    items_count: int = 0
    portals_count: int = 0
    npcs_count: int = 0
    area_name: str = ""
    area_level: int = 0
    monsters_alive_total: int = 0
    entities_monster_count: int = 0
    area_code: str = ""
    area_seed: int = 0
    entities: List[dict] = field(default_factory=list)
    lifecycle_state: CharacterLifecycleState = CharacterLifecycleState.UNINITIALIZED_OR_LOADING


def unpack_telemetry_packet(slot_buf: bytes) -> CoreTelemetrySnapshot:
    """Giải mã nhị phân TelemetryPacket khớp 100% với C++ protocol.hpp (39,112 bytes).
    Single Source of Truth (SSoT) cho việc unpack telemetry ở cả luồng đọc tức thời và worker loop.
    """
    (
        snap_id, ts, paddr, px, py, pz, yaw, pitch,
        cur_hp, max_hp, cur_mana, max_mana, cur_es, max_es,
        cur_ward, max_ward, cur_sp, max_sp, weapon_set, mov_flags, debuff_mask
    ) = struct.unpack_from("<QQQfffffIIIIIIIIIIIIQ", slot_buf, 0)

    area_ts, area_level, area_seed, area_code_bytes, area_name_bytes = struct.unpack_from(
        "<QII32s48s", slot_buf, 39016
    )
    area_code = area_code_bytes.split(b"\x00")[0].decode("utf-8", errors="ignore")
    area_name = area_name_bytes.split(b"\x00")[0].decode("utf-8", errors="ignore")
    pct = (cur_hp / max_hp * 100.0) if max_hp > 0 else 100.0

    entity_count = struct.unpack_from("<I", slot_buf, 100)[0]
    parsed_entities: List[dict] = []
    monsters_cnt = 0
    items_cnt = 0
    portals_cnt = 0
    npcs_cnt = 0

    if 0 < entity_count <= 256:
        read_limit = min(entity_count, 256)
        for i in range(read_limit):
            ent_off = 104 + i * 88
            (
                eid, etype, erarity, ex, ey, ez, edist,
                ehp, emax_hp, estagger, eanim, eflags, ename_b
            ) = struct.unpack_from("<IHHffffIIHHI48s", slot_buf, ent_off)

            ename = ename_b.split(b"\x00")[0].decode("utf-8", errors="ignore")
            if etype == 1:
                monsters_cnt += 1
            elif etype == 2:
                items_cnt += 1
            elif etype == 3:
                portals_cnt += 1
            elif etype == 4:
                npcs_cnt += 1

            parsed_entities.append({
                "id": eid,
                "type": etype,
                "rarity": erarity,
                "pos_x": ex,
                "pos_y": ey,
                "pos_z": ez,
                "dist": edist,
                "hp": ehp,
                "max_hp": emax_hp,
                "stagger": estagger,
                "anim": eanim,
                "flags": eflags,
                "extra_flags": eflags,  # Hỗ trợ tương thích cả key cũ và mới
                "name": ename,
            })

    # Phân định 13 trạng thái CharacterLifecycleState từ mov_flags >> 16
    sub_state = (mov_flags >> 16) & 0xFF
    if sub_state in (1, 2, 4):
        lifecycle_state = CharacterLifecycleState.HIDEOUT_IDLE
    elif sub_state == 3:
        lifecycle_state = CharacterLifecycleState.HIDEOUT_STASHING
    elif sub_state == 5:
        lifecycle_state = CharacterLifecycleState.HIDEOUT_MAP_DEVICE_APPROACH
    elif sub_state == 6:
        lifecycle_state = CharacterLifecycleState.HIDEOUT_MAP_DEVICE_ACTIVATE
    elif sub_state == 7:
        lifecycle_state = CharacterLifecycleState.HIDEOUT_ENTER_PORTAL
    elif sub_state in (8, 9, 10):
        lifecycle_state = (
            CharacterLifecycleState.MAP_LOADING_GRACE if (mov_flags & 16)
            else CharacterLifecycleState.MAP_FARMING_COMBAT
        )
    elif sub_state == 11:
        lifecycle_state = CharacterLifecycleState.MAP_COMPLETED_PORTAL_OUT
    elif sub_state == 12:
        lifecycle_state = CharacterLifecycleState.EMERGENCY_RECOVERY
    elif sub_state == 13:
        lifecycle_state = CharacterLifecycleState.UNINITIALIZED_OR_LOADING
    else:
        # Fallback theo area_name và cờ grace
        if not area_name:
            lifecycle_state = CharacterLifecycleState.UNINITIALIZED_OR_LOADING
        elif any(h in area_name.lower() for h in ("hideout", "town", "encampment", "caravan")):
            lifecycle_state = CharacterLifecycleState.HIDEOUT_IDLE
        else:
            lifecycle_state = (
                CharacterLifecycleState.MAP_LOADING_GRACE if (mov_flags & 16)
                else CharacterLifecycleState.MAP_FARMING_COMBAT
            )

    return CoreTelemetrySnapshot(
        is_online=True,
        snapshot_id=snap_id,
        timestamp_ms=ts,
        player=PlayerTelemetry(
            player_addr=paddr,
            current_hp=cur_hp,
            max_hp=max_hp,
            current_mana=cur_mana,
            max_mana=max_mana,
            current_es=cur_es,
            max_es=max_es,
            current_ward=cur_ward,
            max_ward=max_ward,
            current_spirit=cur_sp,
            max_spirit=max_sp,
            hp_percentage=pct,
            pos_x=px,
            pos_y=py,
            pos_z=pz,
            active_weapon_set=weapon_set,
            movement_flags=mov_flags,
            debuff_mask=debuff_mask,
        ),
        entity_count=entity_count,
        monsters_count=monsters_cnt,
        items_count=items_cnt,
        portals_count=portals_cnt,
        npcs_count=npcs_cnt,
        area_name=area_name,
        area_level=area_level,
        monsters_alive_total=monsters_cnt,
        entities_monster_count=monsters_cnt,
        area_code=area_code,
        area_seed=area_seed,
        entities=parsed_entities,
        lifecycle_state=lifecycle_state,
    )


class CoreShmBridge:
    """
    Kết nối vào khối nhớ chia sẻ IPC của AutoPOE2 Core Engine.
    Đọc dữ liệu Telemetry thời gian thực (30Hz) và phát tín hiệu cảnh báo thụ động.
    """
    def __init__(self, low_hp_threshold: float = 35.0, poll_interval: float = 0.033):
        self.low_hp_threshold = low_hp_threshold
        self.poll_interval = poll_interval

        self._shm: Optional[mmap.mmap] = None
        self._running = False
        self._thread: Optional[threading.Thread] = None
        self._cmd_lock = threading.Lock()
        self._listeners: List[Callable[[CoreTelemetrySnapshot], None]] = []
        self._alert_listeners: List[Callable[[int, int, float], None]] = []

        self.last_snapshot = CoreTelemetrySnapshot()
        self._last_alert_time = 0.0
        self._last_seq = 0

    def register_listener(self, cb: Callable[[CoreTelemetrySnapshot], None]):
        """Đăng ký nhận snapshot telemetry mới (mỗi lần cập nhật)."""
        self._listeners.append(cb)

    def register_low_hp_alert(self, cb: Callable[[int, int, float], None]):
        """Đăng ký nhận cảnh báo khẩn cấp khi HP tụt dưới ngưỡng (current_hp, max_hp, percent)."""
        self._alert_listeners.append(cb)

    def connect(self) -> bool:
        """Kết nối vào khối nhớ chia sẻ IPC (Backward compatibility cho Playbook/Agent)."""
        return self._open_shm()

    def is_connected(self) -> bool:
        """Kiểm tra xem cầu nối SHM đã mở và đang kết nối hay chưa."""
        return bool(self._shm is not None)

    def read_telemetry_snapshot(self) -> CoreTelemetrySnapshot:
        """Đọc và trả về snapshot telemetry tức thời."""
        if not self._shm:
            if not self._open_shm():
                return self.last_snapshot
        self._read_telemetry_once()
        return self.last_snapshot

    def is_heartbeat_alive(self, max_age_ms: int = 4000) -> bool:
        """Kiểm tra xem C++ Core có đang phát heartbeat thời gian thực hay không."""
        if not self._shm:
            if not self._open_shm():
                return False
        try:
            magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb = struct.unpack_from("<QIIQQQQ", self._shm, 0)
            now_ms = int(time.time() * 1000)
            return bool((magic == SHARED_MEM_MAGIC) and (is_running == 1) and (now_ms - core_hb < max_age_ms))
        except Exception:
            return False

    def get_status_dict(self) -> dict:
        """Trả về từ điển trạng thái tổng hợp phục vụ API/Telemetry."""
        alive = self.is_heartbeat_alive()
        snap = self.read_telemetry_snapshot() if alive else self.last_snapshot
        return {
            "shm_connected": self.is_connected(),
            "heartbeat_alive": alive,
            "is_online": snap.is_online,
            "snapshot_id": snap.snapshot_id,
            "timestamp_ms": snap.timestamp_ms,
            "area_name": snap.area_name,
            "area_level": snap.area_level,
            "lifecycle_state": int(snap.lifecycle_state),
            "lifecycle_state_name": snap.lifecycle_state.name,
            "player": {
                "hp": snap.player.current_hp,
                "max_hp": snap.player.max_hp,
                "hp_pct": snap.player.hp_percentage,
                "es": snap.player.current_es,
                "max_es": snap.player.max_es,
                "mana": snap.player.current_mana,
                "max_mana": snap.player.max_mana,
                "pos_x": snap.player.pos_x,
                "pos_y": snap.player.pos_y,
                "pos_z": snap.player.pos_z,
            },
            "monsters_count": snap.monsters_count,
            "items_count": snap.items_count,
        }

    def start(self) -> bool:
        """Khởi động luồng đọc dữ liệu nền."""
        if self._running:
            return True

        self._running = True
        self._thread = threading.Thread(target=self._worker_loop, daemon=True, name="CoreShmBridgeWorker")
        self._thread.start()
        return True

    def stop(self):
        """Dừng luồng đọc dữ liệu."""
        self._running = False
        if self._thread and self._thread.is_alive():
            self._thread.join(timeout=1.0)
        self._close_shm()

    def _open_shm(self) -> bool:
        if self._shm:
            return True
        try:
            self._shm = mmap.mmap(-1, SHM_SIZE, tagname=SHM_NAME)
            # Kiểm tra magic
            header = self._shm[:16]
            magic, proto = struct.unpack("<QI", header[:12])
            if magic != SHARED_MEM_MAGIC or proto != PROTOCOL_VERSION:
                self._close_shm()
                return False
            self._last_seq = 0
            return True
        except Exception:
            self._shm = None
            return False

    def _close_shm(self):
        if self._shm:
            try:
                self._shm.close()
            except Exception:
                pass
            self._shm = None

    def _read_telemetry_once(self) -> bool:
        if not self._shm:
            return False
        try:
            now_ms = int(time.time() * 1000)
            try:
                self._shm[48:56] = struct.pack("<Q", now_ms)
            except Exception:
                self._close_shm()
                return False

            magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack_from("<QIIQQQQQQ", self._shm, 0)
            core_alive = (is_running == 1) and (now_ms - core_hb < 4000)
            if not core_alive:
                if self.last_snapshot.is_online:
                    self.last_snapshot.is_online = False
                return False

            slot_buf = None
            for _ in range(16):
                s1 = struct.unpack_from("<Q", self._shm, 16)[0]
                if s1 == 0:
                    break
                slot = s1 & 1
                slot_offset = 0x1000 if slot == 0 else 0x11000
                data = bytes(self._shm[slot_offset:slot_offset + 39112])
                s2 = struct.unpack_from("<Q", self._shm, 16)[0]
                if s1 == s2:
                    slot_buf = data
                    self._last_seq = s1
                    break

            if not slot_buf:
                return False

            snapshot = unpack_telemetry_packet(slot_buf)
            self.last_snapshot = snapshot
            return True
        except Exception:
            return False

    def _worker_loop(self):
        while self._running:
            try:
                if self._shm is None:
                    if not self._open_shm():
                        if self.last_snapshot.is_online:
                            self.last_snapshot.is_online = False
                            self._notify_snapshot(self.last_snapshot)
                        time.sleep(1.0)
                        continue

                shm = self._shm
                if shm is None:
                    continue

                # 1. Gửi nhịp tim (Agent Heartbeat) vào Header (offset 48)
                now_ms = int(time.time() * 1000)
                try:
                    shm[48:56] = struct.pack("<Q", now_ms)
                except Exception:
                    self._close_shm()
                    continue

                # 2. Đọc Header
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack_from("<QIIQQQQQQ", shm, 0)

                # Kiểm tra Core có còn sống không (nhịp tim < 4000ms)
                core_alive = (is_running == 1) and (now_ms - core_hb < 4000)
                if not core_alive:
                    if self.last_snapshot.is_online:
                        self.last_snapshot.is_online = False
                        self._notify_snapshot(self.last_snapshot)
                    time.sleep(0.5)
                    continue

                # Kiểm tra tránh unpack dữ liệu cũ lặp lại
                if telem_seq == 0 or telem_seq == self._last_seq:
                    time.sleep(self.poll_interval)
                    continue

                # 3. Đọc Telemetry Slot qua Seqlock chống torn-read (tối đa 16 lần thử)
                slot_buf = None
                for _ in range(16):
                    s1 = struct.unpack_from("<Q", shm, 16)[0]
                    if s1 == 0 or s1 == self._last_seq:
                        break
                    slot = s1 & 1
                    slot_offset = 0x1000 if slot == 0 else 0x11000
                    # Copy dữ liệu slot (sizeof(TelemetryPacket) = 39112 bytes)
                    data = bytes(shm[slot_offset:slot_offset + 39112])
                    s2 = struct.unpack_from("<Q", shm, 16)[0]
                    if s1 == s2:
                        slot_buf = data
                        self._last_seq = s1
                        break

                if not slot_buf:
                    time.sleep(self.poll_interval)
                    continue

                snapshot = unpack_telemetry_packet(slot_buf)
                pct = snapshot.player.hp_percentage
                cur_hp = snapshot.player.current_hp
                max_hp = snapshot.player.max_hp

                self.last_snapshot = snapshot
                self._notify_snapshot(snapshot)

                # 4. Kiểm tra cảnh báo Low HP (< threshold)
                if max_hp > 0 and pct < self.low_hp_threshold and cur_hp > 0:
                    now = time.time()
                    if now - self._last_alert_time > 1.8:  # Cooldown 1.8s
                        self._last_alert_time = now
                        self._notify_low_hp(cur_hp, max_hp, pct)

            except Exception:
                # Nếu SHM bị unmap / đóng
                self._close_shm()

            time.sleep(self.poll_interval)

    def _notify_snapshot(self, snapshot: CoreTelemetrySnapshot):
        for cb in self._listeners:
            try:
                cb(snapshot)
            except Exception:
                pass

    def _notify_low_hp(self, cur_hp: int, max_hp: int, pct: float):
        for cb in self._alert_listeners:
            try:
                cb(cur_hp, max_hp, pct)
            except Exception:
                pass

    def send_panic_stop(self) -> bool:
        """Gửi lệnh PANIC_STOP vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)

                # Kiểm tra đầy hàng đợi SPSC ring (16 slot)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId (999), opCode (99 = PANIC_STOP), targetX..targetZ (0.0), targetEntityId (0), priority (0), timeoutMs (0)
                cmd_bytes = struct.pack("<QIfffIII", 999, int(MacroOpCode.PANIC_STOP), 0.0, 0.0, 0.0, 0, 0, 0)
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes

                # Cập nhật cmd_seq
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_cast_emergency_portal(self, timeout_ms: int = 5000) -> bool:
        """Gửi lệnh CAST_EMERGENCY_PORTAL (opCode = 4) vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId, opCode (4), targetX..Z (0.0), targetEntityId (0), priority (4), timeoutMs
                cmd_bytes = struct.pack("<QIfffIII", cmd_seq + 1, int(MacroOpCode.CAST_EMERGENCY_PORTAL), 0.0, 0.0, 0.0, 0, 4, int(timeout_ms))
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_force_portal_return(self, timeout_ms: int = 5000) -> bool:
        """Gửi lệnh FORCE_PORTAL_RETURN (opCode = 11) vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)
                if cmd_seq - cmd_read >= 16:
                    return False
                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64
                cmd_bytes = struct.pack("<QIfffIII", cmd_seq + 1, int(MacroOpCode.FORCE_PORTAL_RETURN), 0.0, 0.0, 0.0, 0, 4, int(timeout_ms))
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_set_combat_profile(self, profile: CombatProfile, timeout_ms: int = 1000) -> bool:
        """Gửi lệnh SET_COMBAT_PROFILE (opCode = 12) vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)
                if cmd_seq - cmd_read >= 16:
                    return False
                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64
                cmd_bytes = struct.pack("<QIfffIII", cmd_seq + 1, int(MacroOpCode.SET_COMBAT_PROFILE), float(int(profile)), 0.0, 0.0, 0, 2, int(timeout_ms))
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_recalibrate_xyz(self) -> bool:
        """Gửi lệnh RECALIBRATE_XYZ (opCode = 98) vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)

                # Kiểm tra đầy hàng đợi SPSC ring (16 slot)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId (998), opCode (98 = RECALIBRATE_XYZ), targetX..targetZ (0), targetEntityId (0), priority (0), timeoutMs (0)
                cmd_bytes = struct.pack("<QIfffIII", 998, int(MacroOpCode.RECALIBRATE_XYZ), 0.0, 0.0, 0.0, 0, 0, 0)
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes

                # Cập nhật cmd_seq
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_recalibrate_vitals(self, max_hp: int, max_mana: int = 0, max_es: int = 0) -> bool:
        """Gửi lệnh hiệu chuẩn lại Vitals (Max HP, Max Mana, Max ES) vào Core Engine qua Command Ring Buffer (opCode = 98)."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)

                # Kiểm tra đầy hàng đợi SPSC ring (16 slot)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId (995), opCode (98 = RECALIBRATE_XYZ), targetX (max_hp), targetY (max_mana), targetZ (max_es)
                cmd_bytes = struct.pack("<QIfffIII", 995, int(MacroOpCode.RECALIBRATE_XYZ), float(max_hp), float(max_mana), float(max_es), 0, 0, 0)
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes

                # Cập nhật cmd_seq
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_trigger_auto_login(self) -> bool:
        """Gửi lệnh TRIGGER_AUTO_LOGIN (opCode = 97) vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)

                # Kiểm tra đầy hàng đợi SPSC ring (16 slot)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId (997), opCode (97 = TRIGGER_AUTO_LOGIN), targetX..targetZ (0), targetEntityId (0), priority (0), timeoutMs (0)
                cmd_bytes = struct.pack("<QIfffIII", 997, int(MacroOpCode.TRIGGER_AUTO_LOGIN), 0.0, 0.0, 0.0, 0, 0, 0)
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes

                # Cập nhật cmd_seq
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_trigger_world_map_travel(self, act: int = 1, zone_index: int = 0) -> bool:
        """Gửi lệnh TRIGGER_WORLD_MAP_TRAVEL (opCode = 96) vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)

                # Kiểm tra đầy hàng đợi SPSC ring (16 slot)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId (996), opCode (96 = TRIGGER_WORLD_MAP_TRAVEL), targetX (act), targetY (zone_index), targetZ (0), targetEntityId (0), priority (1), timeoutMs (5000)
                cmd_bytes = struct.pack("<QIfffIII", 996, int(MacroOpCode.TRIGGER_WORLD_MAP_TRAVEL), float(act), float(zone_index), 0.0, 0, 1, 5000)
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes

                # Cập nhật cmd_seq
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_set_map_scenario(self, scenario: int = 0, auto_portal: bool = True) -> bool:
        """Gửi lệnh SET_MAP_SCENARIO (opCode = 95) vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)

                # Kiểm tra đầy hàng đợi SPSC ring (16 slot)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId (994), opCode (95 = SET_MAP_SCENARIO), targetX (scenario), targetY (1.0 if auto_portal else 0.0), targetZ (0), targetEntityId (0), priority (2), timeoutMs (1000)
                cmd_bytes = struct.pack("<QIfffIII", 994, int(MacroOpCode.SET_MAP_SCENARIO), float(scenario), 1.0 if auto_portal else 0.0, 0.0, 0, 2, 1000)
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes

                # Cập nhật cmd_seq
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_npc_interact(self, npc_id: int = 0, mode: int = 1, option_idx: int = 0, timeout_ms: int = 15000) -> bool:
        """Gửi lệnh EXECUTE_NPC_INTERACT_ROUTINE (opCode = 8) vào Core Engine (mode 1 = IdentifyAll, 0 = Skip)."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId, opCode (8), targetX (mode), targetY (option_idx), targetZ (0), targetEntityId (npc_id), priority (3), timeoutMs
                cmd_bytes = struct.pack("<QIfffIII", cmd_seq + 1, int(MacroOpCode.EXECUTE_NPC_INTERACT_ROUTINE), float(mode), float(option_idx), 0.0, int(npc_id), 3, int(timeout_ms))
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_map_device(self, waystone_col: int = 0, waystone_row: int = 0, use_tablet: bool = False, timeout_ms: int = 20000) -> bool:
        """Gửi lệnh EXECUTE_MAP_DEVICE_ROUTINE (opCode = 7) vào Core Engine qua Command Ring Buffer."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId, opCode (7), targetX (col), targetY (row), targetZ (1.0 if tablet else 0.0), targetEntityId (0), priority (3), timeoutMs
                cmd_bytes = struct.pack("<QIfffIII", cmd_seq + 1, int(MacroOpCode.EXECUTE_MAP_DEVICE_ROUTINE), float(waystone_col), float(waystone_row), 1.0 if use_tablet else 0.0, 0, 3, int(timeout_ms))
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_select_atlas_node(self, screen_x: float, screen_y: float, flags: int = 1, node_id: int = 0, timeout_ms: int = 5000) -> bool:
        """Gửi lệnh SELECT_ATLAS_NODE (opCode = 9) vào Core Engine qua Command Ring Buffer (Doc 56)."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId, opCode (9), targetX (screen_x), targetY (screen_y), targetZ (flags), targetEntityId (node_id), priority (3200), timeoutMs (5000)
                cmd_bytes = struct.pack("<QIfffIII", cmd_seq + 1, int(MacroOpCode.SELECT_ATLAS_NODE), float(screen_x), float(screen_y), float(flags), int(node_id), 3200, int(timeout_ms))
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

    def send_verify_map_device_ui(self, timeout_ms: int = 5000) -> bool:
        """Gửi lệnh VERIFY_MAP_DEVICE_UI (opCode = 10) vào Core Engine qua Command Ring Buffer (INV-MAPDEVICE-VERIFY-UI)."""
        if not self._shm:
            return False
        with self._cmd_lock:
            try:
                header_data = self._shm[:64]
                magic, proto, is_running, telem_seq, cmd_seq, cmd_read, core_hb, agent_hb, _ = struct.unpack("<QIIQQQQQQ", header_data)
                if cmd_seq - cmd_read >= 16:
                    return False

                slot_idx = cmd_seq % 16
                slot_off = 0x300000 + slot_idx * 64

                # CommandPacket: commandId, opCode (10), targetX (0), targetY (0), targetZ (0), targetEntityId (0), priority (3200), timeoutMs (5000)
                cmd_bytes = struct.pack("<QIfffIII", cmd_seq + 1, int(MacroOpCode.VERIFY_MAP_DEVICE_UI), 0.0, 0.0, 0.0, 0, 3200, int(timeout_ms))
                cmd_bytes = cmd_bytes.ljust(64, b"\x00")
                self._shm[slot_off:slot_off + 64] = cmd_bytes
                self._shm[24:32] = struct.pack("<Q", cmd_seq + 1)
                return True
            except Exception:
                return False

# Backward-compatibility alias for documentation & scripts
CoreSharedMemoryBridge = CoreShmBridge


