"""
功能范围:
- 通过 `device.enter_dfu_mode()` 将连接的 MWDrive 置于供应商 DFU 模式。
- 尽可能检测供应商（HPM / Nations GM）。
- 使用供应商工具烧录 BIN 固件:
  - HPM: `hpm_dfu.exe`
  - GM : `NsMcuCmdDll.dll`
"""

from __future__ import annotations

import os
import subprocess
import time
from pathlib import Path
from types import SimpleNamespace
from typing import Literal, Optional


Vendor = Literal["hpm", "gm"]

# HPM default VID/PID used by the skill. Detection uses the Windows PnP ID.
DEFAULT_HPM_USB_ID = "0x34b7,0x0006"
DEFAULT_HPM_PNP_PATTERN = "VID_34B7&PID_0006"

# Environment variable overrides (useful for installed package usage)
ENV_ASSETS_DIR = "MWDRIVE_UPGRADE_ASSETS_DIR"
ENV_HPM_DFU_EXE = "MWDRIVE_HPM_DFU_EXE"
ENV_GM_TOOL_DIR = "MWDRIVE_GM_TOOL_DIR"


def _default_assets_dir() -> Optional[Path]:
    """
    Best-effort assets dir resolver.

    Priority:
    - MWDRIVE_UPGRADE_ASSETS_DIR
    - ./assets (current working directory)
    - Packaged vendor assets inside the mwdrive package
    - <repo_root>/odrive-firmware-upgrader/assets (when developing in this workspace)
    """
    env = os.environ.get(ENV_ASSETS_DIR)
    if env:
        p = Path(env).expanduser()
        if p.exists():
            return p

    cwd_assets = Path.cwd() / "assets"
    if cwd_assets.exists():
        return cwd_assets

    # Packaged vendor assets alongside this module:
    #   mwdrive/firmware_upgrade.py
    #   mwdrive/vendor_assets/...
    try:
        this_file = Path(__file__).resolve()
        package_root = this_file.parent
        packaged_assets = package_root / "vendor_assets"
        if packaged_assets.exists():
            return packaged_assets
    except Exception:
        pass

    # Heuristic for this repo layout during development:
    # mwdrive-1.0.14/mwdrive/firmware_upgrade.py
    # -> mwdrive-1.0.14 (parent 1)
    # -> workspace root (parent 2)
    # -> odrive-firmware-upgrader/assets
    try:
        this_file = Path(__file__).resolve()
        workspace_root = this_file.parent.parent.parent
        candidate = workspace_root / "odrive-firmware-upgrader" / "assets"
        if candidate.exists():
            return candidate
    except Exception:
        pass

    return None


def _resolve_tool_paths(
    *,
    assets_dir: Optional[str | os.PathLike[str]] = None,
    hpm_dfu_exe: Optional[str | os.PathLike[str]] = None,
    gm_tool_dir: Optional[str | os.PathLike[str]] = None,
) -> tuple[Optional[Path], Optional[Path], Optional[Path]]:
    """
    Returns (assets_dir, hpm_dfu_exe, gm_tool_dir) as Paths (may be None).
    """
    assets_p: Optional[Path]
    if assets_dir is not None:
        assets_p = Path(assets_dir).expanduser().resolve()
    else:
        assets_p = _default_assets_dir()
        assets_p = assets_p.resolve() if assets_p else None

    hpm_exe_p: Optional[Path]
    if hpm_dfu_exe is not None:
        hpm_exe_p = Path(hpm_dfu_exe).expanduser().resolve()
    else:
        env = os.environ.get(ENV_HPM_DFU_EXE)
        if env:
            hpm_exe_p = Path(env).expanduser().resolve()
        elif assets_p:
            hpm_exe_p = assets_p / "hpm_dfu" / "hpm_dfu.exe"
        else:
            hpm_exe_p = None

    gm_dir_p: Optional[Path]
    if gm_tool_dir is not None:
        gm_dir_p = Path(gm_tool_dir).expanduser().resolve()
    else:
        env = os.environ.get(ENV_GM_TOOL_DIR)
        if env:
            gm_dir_p = Path(env).expanduser().resolve()
        elif assets_p:
            gm_dir_p = assets_p / "NsMcuCmdDll-x64-20241016"
        else:
            gm_dir_p = None

    return assets_p, hpm_exe_p, gm_dir_p


def _has_hpm_usb_board(pnp_pattern: str = DEFAULT_HPM_PNP_PATTERN, verbose: bool = True) -> bool:
    """
    Windows-only: detects HPM DFU device by matching VID/PID in PnP InstanceId.
    """
    if os.name != "nt":
        return False

    # Prefer wmic (older systems) then fallback to PowerShell.
    wmic_cmd = [
        "wmic",
        "path",
        "Win32_PnPEntity",
        "where",
        f"DeviceID like '%{pnp_pattern}%'",
        "get",
        "DeviceID",
    ]
    try:
        result = subprocess.run(wmic_cmd, capture_output=True, text=True, check=False)
        combined = (result.stdout or "") + (result.stderr or "")
        if pnp_pattern.lower() in combined.lower():
            if verbose:
                print(f"[INFO] 通过 WMIC 检测到 HPM DFU 设备 (模式 {pnp_pattern})。")
            return True
    except FileNotFoundError:
        pass

    ps_script = (
        "Get-PnpDevice -PresentOnly | "
        f"Where-Object {{$_.InstanceId -like '*{pnp_pattern}*'}}"
    )
    ps_cmd = ["powershell", "-NoProfile", "-Command", ps_script]
    try:
        result = subprocess.run(ps_cmd, capture_output=True, text=True, check=False)
        combined = (result.stdout or "") + (result.stderr or "")
        if result.returncode == 0 and pnp_pattern.lower() in combined.lower():
            if verbose:
                print(f"[INFO] 通过 PowerShell 检测到 HPM DFU 设备 (模式 {pnp_pattern})。")
            return True
    except FileNotFoundError:
        if verbose:
            print("[WARN] WMIC 和 PowerShell 都不可用；无法自动检测 HPM 设备。")

    return False


def _gm_dll_path(gm_tool_dir: Optional[Path]) -> Optional[Path]:
    if gm_tool_dir is None:
        return None
    dll = gm_tool_dir / "NsMcuCmdDll.dll"
    return dll if dll.exists() else None


def _gm_bindings(gm_tool_dir: Path) -> SimpleNamespace:
    """
    Load NsMcuCmdDll.dll and return a namespace with common ctypes
    structures and functions bound.

    This helper is used by both `flash_with_gm_vendor` and
    `erase_all_flash_only` to avoid duplicating ctypes definitions.
    """
    dll_path = _gm_dll_path(gm_tool_dir)
    if not dll_path:
        raise FileNotFoundError(f"NsMcuCmdDll.dll not found under: {gm_tool_dir}")

    from ctypes import (  # noqa: PLC0415
        POINTER,
        Structure,
        byref,
        c_float,
        c_uint8,
        c_uint32,
        c_void_p,
        c_wchar_p,
        cast,
        cdll,
    )

    print(f"[INFO] [GM] 加载 DLL: {dll_path}")
    dll = cdll.LoadLibrary(str(dll_path))

    OK_CODE = 0xA000
    CHIP_TYPE_LEN = 16
    UCID_LEN = 16
    UID_LEN = 12
    KEY_SIZE = 16

    class NS_ChipInfo(Structure):
        _fields_ = [
            ("nChipType", c_uint8),
            ("ChipType", c_uint8 * CHIP_TYPE_LEN),
            ("nChipTypeLen", c_uint8),
            ("UCID", c_uint8 * UCID_LEN),
            ("nUCIDLen", c_uint8),
            ("UID", c_uint8 * UID_LEN),
            ("nUIDLen", c_uint8),
            ("nBootCommVer", c_uint8),
            ("nBootVer", c_uint8),
            ("nFlashSize", c_float),  # KB
            ("nPartitionSizeMin", c_uint8),
            ("nPageSize", c_uint32),
        ]

    class NS_PartitionInfo(Structure):
        _fields_ = [
            ("rwFlag", c_uint8),
            ("userId", c_uint8),
            ("size", c_uint8),
            ("keyId", c_uint8),
            ("mode", c_uint8),
        ]

    class NS_ChipKey(Structure):
        _fields_ = [
            ("keyId", c_uint8),
            ("newKey", c_uint8 * KEY_SIZE),
            ("oldKey", c_uint8 * KEY_SIZE),
        ]

    class NS_Dl_File(Structure):
        _fields_ = [
            ("userId", c_uint8),
            ("nStartAddr", c_uint32),
            ("pFileBuff", c_void_p),
            ("nFileLen", c_uint32),
            ("nFileAttr", c_uint8),
        ]

    HANDLE = c_void_p

    ns_enum_dev = dll.NS_CMD_ENUM_DEV
    ns_connect = dll.NS_CMD_CONNECT_DEV
    ns_get_inf = dll.NS_CMD_GET_INF
    ns_erase = dll.NS_CMD_FLASH_ERASE
    ns_download = getattr(dll, "NS_CMD_FLASH_DWNLD", None)
    ns_reset = getattr(dll, "NS_CMD_SYS_RESET", None)

    ns_enum_dev.restype = c_uint8
    ns_enum_dev.argtypes = [c_uint8, POINTER(c_wchar_p)]

    ns_connect.restype = c_uint8
    ns_connect.argtypes = [POINTER(HANDLE), c_wchar_p, c_uint32]

    ns_get_inf.restype = c_uint32
    ns_get_inf.argtypes = [HANDLE, POINTER(NS_ChipInfo)]

    ns_erase.restype = c_uint32
    ns_erase.argtypes = [HANDLE, c_uint32, c_uint32, POINTER(NS_PartitionInfo), POINTER(NS_ChipKey)]

    if ns_download is not None:
        ns_download.restype = c_uint32
        ns_download.argtypes = [
            HANDLE,
            POINTER(NS_Dl_File),
            POINTER(NS_PartitionInfo),
            POINTER(NS_ChipKey),
            c_void_p,
            c_void_p,
        ]

    if ns_reset is not None:
        ns_reset.restype = c_uint32
        ns_reset.argtypes = [HANDLE]

    return SimpleNamespace(
        dll=dll,
        HANDLE=HANDLE,
        NS_ChipInfo=NS_ChipInfo,
        NS_PartitionInfo=NS_PartitionInfo,
        NS_ChipKey=NS_ChipKey,
        NS_Dl_File=NS_Dl_File,
        ns_enum_dev=ns_enum_dev,
        ns_connect=ns_connect,
        ns_get_inf=ns_get_inf,
        ns_erase=ns_erase,
        ns_download=ns_download,
        ns_reset=ns_reset,
        OK_CODE=OK_CODE,
    )


def _has_gm_usb_board(gm_tool_dir: Optional[Path], verbose: bool = True) -> bool:
    """
    Windows-only: uses Nations DLL `NS_CMD_ENUM_DEV` to check USB(DFU) devices.
    Returns False if DLL is missing/unloadable.
    """
    if os.name != "nt":
        return False

    dll_path = _gm_dll_path(gm_tool_dir)
    if not dll_path:
        if verbose:
            print("[DEBUG] 未找到 NsMcuCmdDll.dll；跳过 GM 自动检测。")
        return False

    from ctypes import POINTER, c_uint8, c_wchar_p, cdll  # noqa: PLC0415

    try:
        dll = cdll.LoadLibrary(str(dll_path))
    except OSError as exc:
        if verbose:
            print(f"[WARN] 加载 NsMcuCmdDll.dll 失败: {exc}")
        return False

    try:
        enum_func = dll.NS_CMD_ENUM_DEV
    except AttributeError:
        if verbose:
            print("[WARN] NsMcuCmdDll.dll 缺少 NS_CMD_ENUM_DEV；无法自动检测 GM 设备。")
        return False

    enum_func.restype = c_uint8
    enum_func.argtypes = [c_uint8, POINTER(c_wchar_p)]

    max_dev = 16
    DeviceArray = c_wchar_p * max_dev
    devices = DeviceArray()

    # nInterFace = 1 means USB(DFU)
    count = int(enum_func(c_uint8(1), devices))
    if count > 0:
        if verbose:
            print(f"[INFO] 通过 NsMcuCmdDll.dll 检测到 {count} 个 GM USB(DFU) 设备。")
        return True

    if verbose:
        print("[DEBUG] NsMcuCmdDll.dll 枚举返回 0；未找到 GM USB(DFU) 设备。")
    return False


def wait_for_vendor_dfu_present(
    *,
    gm_tool_dir: Optional[Path],
    timeout: float = 1.0,
    interval: float = 0.2,
) -> bool:
    """
    Polls for presence of vendor DFU devices (HPM or GM).
    """
    deadline = time.time() + timeout
    attempt = 1
    while time.time() < deadline:
        if _has_hpm_usb_board(verbose=False) or _has_gm_usb_board(gm_tool_dir, verbose=False):
            print(f"[INFO] 在第 {attempt} 次尝试时检测到 DFU 设备。")
            return True
        attempt += 1
        time.sleep(interval)

    print("[INFO] 在超时时间内未检测到供应商 DFU 设备。")
    return False


def enter_vendor_dfu_mode(
    *,
    path: Optional[str] = None,
    serial_number: Optional[str] = None,
) -> None:
    """
    Uses mwdrive discovery to find a device in RUN mode and requests it to enter
    vendor DFU mode via `device.enter_dfu_mode()`.
    """
    import mwdrive  # local import to avoid heavy import when unused

    search_path = path or getattr(mwdrive, "default_usb_search_path", None)
    if not search_path:
        raise RuntimeError("mwdrive.default_usb_search_path not available; please pass --path explicitly.")

    print(f"[INFO] 通过 mwdrive 发现设备 (路径={search_path!r}, 序列号={serial_number!r}) ...")
    mwdrive.start_discovery(search_path)
    time.sleep(1.5)

    try:
        dev = mwdrive.find_any(path=search_path, serial_number=serial_number, timeout=10.0)
    except TypeError:
        # Some forks may not support timeout kwarg
        dev = mwdrive.find_any(path=search_path, serial_number=serial_number)
    except TimeoutError:
        # Give user a friendly hint about likely power issue instead of a raw traceback
        print(
            "[ERROR] 超时时间内 mwdrive 未发现任何设备。"
            "请检查设备是否已接通电源并正确连接 USB，然后重试。"
        )
        raise RuntimeError("mwdrive 未发现设备：您可能未接电源或 USB 未连接。")

    if not dev:
        raise RuntimeError("No device found via mwdrive. Check USB/power/driver.")

    print("[INFO] 发送 DFU 请求: device.enter_dfu_mode() ...")
    try:
        dev.enter_dfu_mode()
    except Exception as exc:  # noqa: BLE001
        msg = str(exc).lower()
        # Device re-enumeration can surface as object lost; treat as success.
        if any(k in msg for k in ["disappear", "disappeared", "no device", "not found", "lost"]):
            print(f"[WARN] 设备断开连接 (进入 DFU 模式时预期): {exc}")
        else:
            raise

    time.sleep(2.0)
    print("[SUCCESS] DFU 命令已发送。设备应重新枚举为供应商 DFU。")


def detect_vendor(*, gm_tool_dir: Optional[Path]) -> Optional[Vendor]:
    """
    Detects vendor based on:
    - HPM: Windows PnP VID/PID pattern
    - GM : Nations DLL enumeration
    """
    print("[INFO] 自动检测供应商 (HPM / GM) ...")
    if _has_hpm_usb_board():
        return "hpm"
    if _has_gm_usb_board(gm_tool_dir):
        return "gm"
    print("[WARN] 供应商自动检测失败。请指定 --vendor hpm|gm。")
    return None


def flash_with_hpm(
    *,
    bin_path: Path,
    hpm_dfu_exe: Path,
    hpm_usb_id: str = DEFAULT_HPM_USB_ID,
    erase_all: bool = False,
) -> None:
    if not hpm_dfu_exe.exists():
        raise FileNotFoundError(f"hpm_dfu.exe not found: {hpm_dfu_exe}")
    bin_path = bin_path.resolve()
    if not bin_path.is_file():
        raise FileNotFoundError(f"BIN firmware not found: {bin_path}")

    base_cmd = [
        str(hpm_dfu_exe),
        "-u",
        hpm_usb_id,
        "-r",
        "write-memory 0x0 0x200 [[0xfcf90002,0x00000007,0x00001000,0x00000000]]",
        "-r",
        "config-memory 0x10000 0x200",
    ]
    print("[STEP] [HPM] 配置 DFU ...")
    subprocess.run(base_cmd, check=True)

    if erase_all:
        erase_cmd = [str(hpm_dfu_exe), "-u", hpm_usb_id, "-r", "erase-chip 0x10000"]
        print("[STEP] [HPM] 擦除芯片 ...")
        subprocess.run(erase_cmd, check=True)

    write_cmd = [
        str(hpm_dfu_exe),
        "-u",
        hpm_usb_id,
        "-r",
        f"write-memory 0x10000 0x80001000 {bin_path}",
    ]
    print(f"[STEP] [HPM] 烧录 BIN: {bin_path}")
    subprocess.run(write_cmd, check=True)

    reset_cmd = [str(hpm_dfu_exe), "-u", hpm_usb_id, "-r", "reset 0"]
    print("[STEP] [HPM] 重置设备 ...")
    subprocess.run(reset_cmd, check=True)
    print("[SUCCESS] [HPM] 固件升级完成。")


def flash_with_gm_vendor(
    *,
    bin_path: Path,
    gm_tool_dir: Path,
    erase_all: bool = False,
) -> None:
    """
    Uses Nations `NsMcuCmdDll.dll` to erase + download + reset.
    """
    bin_path = bin_path.resolve()
    if not bin_path.is_file():
        raise FileNotFoundError(f"BIN firmware not found: {bin_path}")

    from ctypes import byref, c_uint8, c_uint32, c_void_p, c_wchar_p, cast  # noqa: PLC0415

    gm = _gm_bindings(gm_tool_dir)

    max_dev = 16
    DeviceArray = c_wchar_p * max_dev
    devices = DeviceArray()

    print("[STEP] [GM] 枚举 USB(DFU) 设备 ...")
    count = int(gm.ns_enum_dev(c_uint8(1), devices))
    if count <= 0 or not devices[0]:
        raise RuntimeError("NsMcuCmdDll.dll could not find any USB(DFU) devices.")
    dev_name = devices[0]
    print(f"[INFO] [GM] 使用设备: {dev_name!r}")

    handle = gm.HANDLE()
    ret = int(gm.ns_connect(byref(handle), dev_name, c_uint32(9600)))
    if ret not in (0, gm.OK_CODE) or not handle:
        raise RuntimeError(f"[GM] NS_CMD_CONNECT_DEV failed: 0x{ret:04X}")

    chip_info = gm.NS_ChipInfo()
    ret_info = int(gm.ns_get_inf(handle, byref(chip_info)))
    if ret_info not in (0, gm.OK_CODE):
        raise RuntimeError(f"[GM] NS_CMD_GET_INF failed: 0x{ret_info:04X}")

    flash_kb = float(chip_info.nFlashSize)
    print(f"[INFO] [GM] 芯片信息: FlashSize={flash_kb:.1f} KB, PageSize={chip_info.nPageSize} bytes")

    FLASH_BASE = 0x08000000
    file_size = bin_path.stat().st_size
    if erase_all:
        total_bytes = int(flash_kb * 1024)
        start_addr = c_uint32(FLASH_BASE)
        end_addr = c_uint32(FLASH_BASE + total_bytes - 1)
        print(f"[STEP] [GM] 擦除完整闪存: 0x{start_addr.value:08X}-0x{end_addr.value:08X}")
    else:
        start_addr = c_uint32(FLASH_BASE)
        end_addr = c_uint32(FLASH_BASE + file_size - 1)
        print(f"[STEP] [GM] 按文件大小擦除: 0x{start_addr.value:08X}-0x{end_addr.value:08X} ({file_size} 字节)")

    ret_erase = int(gm.ns_erase(handle, start_addr, end_addr, None, None))
    if ret_erase not in (0, gm.OK_CODE):
        raise RuntimeError(f"[GM] NS_CMD_FLASH_ERASE failed: 0x{ret_erase:04X}")

    fw = bin_path.read_bytes()
    aligned_len = (len(fw) + 15) & ~15
    padded = fw + (b"\xFF" * (aligned_len - len(fw)))
    buf_type = c_uint8 * aligned_len
    buf = buf_type(*padded)

    if gm.ns_download is None:
        raise RuntimeError("[GM] NsMcuCmdDll.dll missing NS_CMD_FLASH_DWNLD")

    dl = gm.NS_Dl_File()
    dl.userId = 0
    dl.nStartAddr = FLASH_BASE
    dl.pFileBuff = cast(buf, c_void_p)
    dl.nFileLen = aligned_len
    dl.nFileAttr = 0

    print(f"[STEP] [GM] 下载固件 ({len(fw)} 字节，对齐 {aligned_len}) ...")
    ret_dl = int(gm.ns_download(handle, byref(dl), None, None, None, None))
    if ret_dl not in (0, gm.OK_CODE):
        raise RuntimeError(f"[GM] NS_CMD_FLASH_DWNLD failed: 0x{ret_dl:04X}")

    if gm.ns_reset is None:
        raise RuntimeError("[GM] NsMcuCmdDll.dll missing NS_CMD_SYS_RESET")

    print("[STEP] [GM] 重置设备 ...")
    ret_reset = int(gm.ns_reset(handle))
    if ret_reset not in (0, gm.OK_CODE):
        raise RuntimeError(f"[GM] NS_CMD_SYS_RESET failed: 0x{ret_reset:04X}")

    print("[SUCCESS] [GM] 固件升级完成。")


def upgrade_firmware(
    *,
    bin_path: str | os.PathLike[str],
    vendor: Literal["auto", "hpm", "gm"] = "auto",
    erase_all: bool = False,
    path: Optional[str] = None,
    serial_number: Optional[str] = None,
    assets_dir: Optional[str | os.PathLike[str]] = None,
    hpm_dfu_exe: Optional[str | os.PathLike[str]] = None,
    gm_tool_dir: Optional[str | os.PathLike[str]] = None,
    hpm_usb_id: str = DEFAULT_HPM_USB_ID,
) -> Vendor:
    """
    Single-shot upgrade flow.

    Returns detected/selected vendor ("hpm" or "gm") on success.
    """
    bin_p = Path(bin_path).expanduser().resolve()

    assets_p, hpm_exe_p, gm_dir_p = _resolve_tool_paths(
        assets_dir=assets_dir,
        hpm_dfu_exe=hpm_dfu_exe,
        gm_tool_dir=gm_tool_dir,
    )

    if assets_p:
        print(f"[INFO] 资源目录: {assets_p}")

    print("[STEP] 1/3 检查设备是否已在供应商 DFU 模式 ...")
    already_in_dfu = wait_for_vendor_dfu_present(gm_tool_dir=gm_dir_p, timeout=1.0, interval=0.2)
    if not already_in_dfu:
        print("[INFO] 不在 DFU 模式；通过 mwdrive 请求设备进入 DFU 模式 ...")
        enter_vendor_dfu_mode(path=path, serial_number=serial_number)

    print("[STEP] 2/3 确定供应商 ...")
    if vendor == "auto":
        v = detect_vendor(gm_tool_dir=gm_dir_p)
        if v is None:
            raise RuntimeError("Vendor auto-detection failed. Use --vendor hpm|gm.")
    else:
        v = vendor  # type: ignore[assignment]

    print(f"[INFO] 选择的供应商: {v}")

    print("[STEP] 3/3 烧录固件 ...")
    if v == "hpm":
        if not hpm_exe_p:
            raise RuntimeError(
                "hpm_dfu.exe path not configured. Provide --hpm-dfu-exe, or --assets-dir, "
                f"or set env {ENV_HPM_DFU_EXE}."
            )
        flash_with_hpm(bin_path=bin_p, hpm_dfu_exe=hpm_exe_p, hpm_usb_id=hpm_usb_id, erase_all=erase_all)
        return "hpm"

    if v == "gm":
        if not gm_dir_p:
            raise RuntimeError(
                "GM tool dir not configured. Provide --gm-tool-dir, or --assets-dir, "
                f"or set env {ENV_GM_TOOL_DIR}."
            )
        flash_with_gm_vendor(bin_path=bin_p, gm_tool_dir=gm_dir_p, erase_all=erase_all)
        return "gm"

    # Should be unreachable
    raise RuntimeError(f"Unsupported vendor: {v}")


def erase_all_flash_only(
    *,
    vendor: Literal["auto", "hpm", "gm"] = "auto",
    path: Optional[str] = None,
    serial_number: Optional[str] = None,
    assets_dir: Optional[str | os.PathLike[str]] = None,
    hpm_dfu_exe: Optional[str | os.PathLike[str]] = None,
    gm_tool_dir: Optional[str | os.PathLike[str]] = None,
    hpm_usb_id: str = DEFAULT_HPM_USB_ID,
) -> Vendor:
    """
    Enter vendor DFU mode (if needed) and erase the entire flash, but do NOT
    program new firmware and do NOT reset the device.

    This is intended for workflows where you want to wipe all configuration and
    firmware first, and perform the actual programming step later with a
    separate command or tool.
    """
    assets_p, hpm_exe_p, gm_dir_p = _resolve_tool_paths(
        assets_dir=assets_dir,
        hpm_dfu_exe=hpm_dfu_exe,
        gm_tool_dir=gm_tool_dir,
    )

    if assets_p:
        print(f"[INFO] 资源目录: {assets_p}")

    print("[STEP] 1/2 检查设备是否已在供应商 DFU 模式 ...")
    already_in_dfu = wait_for_vendor_dfu_present(gm_tool_dir=gm_dir_p, timeout=1.0, interval=0.2)
    if not already_in_dfu:
        print("[INFO] 不在 DFU 模式；通过 mwdrive 请求设备进入 DFU 模式 ...")
        enter_vendor_dfu_mode(path=path, serial_number=serial_number)

    print("[STEP] 2/2 确定供应商并执行全擦除...")
    if vendor == "auto":
        v = detect_vendor(gm_tool_dir=gm_dir_p)
        if v is None:
            raise RuntimeError("Vendor auto-detection failed. Use --vendor hpm|gm.")
    else:
        v = vendor  # type: ignore[assignment]

    print(f"[INFO] 选择的供应商: {v}")

    if v == "hpm":
        if not hpm_exe_p:
            raise RuntimeError(
                "hpm_dfu.exe path not configured. Provide --hpm-dfu-exe, or --assets-dir, "
                f"or set env {ENV_HPM_DFU_EXE}."
            )
        if not hpm_exe_p.exists():
            raise FileNotFoundError(f"hpm_dfu.exe not found: {hpm_exe_p}")

        erase_cmd = [str(hpm_exe_p), "-u", hpm_usb_id, "-r", "erase-chip 0x10000"]
        print("[STEP] [HPM] 仅执行全芯片擦除...")
        subprocess.run(erase_cmd, check=True)
        print("[SUCCESS] [HPM] 全芯片擦除完成。设备保持在 DFU 模式，等待后续操作。")
        return "hpm"

    if v == "gm":
        if not gm_dir_p:
            raise RuntimeError(
                "GM tool dir not configured. Provide --gm-tool-dir, or --assets-dir, "
                f"or set env {ENV_GM_TOOL_DIR}."
            )

        from ctypes import byref, c_uint8, c_uint32, c_wchar_p, c_void_p  # noqa: PLC0415

        gm = _gm_bindings(gm_dir_p)

        max_dev = 16
        DeviceArray = c_wchar_p * max_dev
        devices = DeviceArray()

        print("[STEP] [GM] 枚举 USB(DFU) 设备 ...")
        count = int(gm.ns_enum_dev(c_uint8(1), devices))
        if count <= 0 or not devices[0]:
            raise RuntimeError("NsMcuCmdDll.dll could not find any USB(DFU) devices.")
        dev_name = devices[0]
        print(f"[INFO] [GM] 使用设备: {dev_name!r}")

        handle = gm.HANDLE()
        ret = int(gm.ns_connect(byref(handle), dev_name, c_uint32(9600)))
        if ret not in (0, gm.OK_CODE) or not handle:
            raise RuntimeError(f"[GM] NS_CMD_CONNECT_DEV failed: 0x{ret:04X}")

        chip_info = gm.NS_ChipInfo()
        ret_info = int(gm.ns_get_inf(handle, byref(chip_info)))
        if ret_info not in (0, gm.OK_CODE):
            raise RuntimeError(f"[GM] NS_CMD_GET_INF failed: 0x{ret_info:04X}")

        flash_kb = float(chip_info.nFlashSize)
        print(f"[INFO] [GM] 芯片信息: FlashSize={flash_kb:.1f} KB, PageSize={chip_info.nPageSize} bytes")

        FLASH_BASE = 0x08000000
        total_bytes = int(flash_kb * 1024)
        start_addr = c_uint32(FLASH_BASE)
        end_addr = c_uint32(FLASH_BASE + total_bytes - 1)
        print(f"[STEP] [GM] 全芯片擦除: 0x{start_addr.value:08X}-0x{end_addr.value:08X}")

        ret_erase = int(gm.ns_erase(handle, start_addr, end_addr, None, None))
        if ret_erase not in (0, gm.OK_CODE):
            raise RuntimeError(f"[GM] NS_CMD_FLASH_ERASE failed: 0x{ret_erase:04X}")

        print("[SUCCESS] [GM] 全芯片擦除完成。设备保持在 DFU 模式，等待后续操作。")
        return "gm"

    raise RuntimeError(f"Unsupported vendor: {v}")
