"""Material-only cleanup definitions and read-only inventory aggregation."""
from functools import lru_cache
import json

from .errors import MiniError, require
from .paths import resource_dir


# Exact official McpItem.Category from the retained get_items response.
# CategoryDisplayName or a name containing 'material' is not a control ID.
MATERIAL_CATEGORY = "Ingredient"
MAX_KEEP = 2147483647
LOCATIONS = ("inventory", "character_storage", "account_storage")


@lru_cache(maxsize=2)
def _material_rules(path, modified_ns, size):
    document = json.loads(path.read_text(encoding="utf-8"))
    require(isinstance(document, dict) and document.get("schema") == 1 and isinstance(document.get("materials"), dict),
            "가방정리 재료 원본 규칙 형식을 확인해 주세요.")
    return document["materials"]


def item_rule(name):
    """Exact Ingredient sheet rule; missing data is never a guessed action."""
    path = resource_dir() / "bag-cleanup-materials.json"
    try:
        stat = path.stat()
        rules = _material_rules(path, stat.st_mtime_ns, stat.st_size)
    except (OSError, UnicodeError, json.JSONDecodeError) as exc:
        raise MiniError("가방정리 재료 원본 규칙 파일을 읽지 못했습니다. bag-cleanup-materials.json을 포함한 업데이트를 적용해 주세요.") from exc
    row = rules.get(name)
    require(isinstance(row, dict) and row.get("action") in ("sell", "discard", "unavailable")
            and type(row.get("warning")) is bool,
            name + " · 이 재료의 판매/버리기 원본 규칙이 없습니다. 규칙을 갱신해 주세요.")
    if row["action"] != "unavailable":
        roles = row.get("detail_roles")
        require(isinstance(roles, list) and roles and roles[0] == "Lock"
                and all(isinstance(role, str) for role in roles)
                and len(roles) == len(set(roles))
                and set(roles) <= {"Lock", "Disassemble", "Sell", "Discard", "Alchemy", "EquipQuickSlot", "UnequipQuickSlot", "Exchange", "GoToCrafting"}
                and row["action"].title() in roles
                and not ({"Sell", "Discard"} <= set(roles)),
                name + " · 준비된 상세 버튼 경로가 없습니다. 재료 규칙을 갱신해 주세요.")
    return dict(row)


def is_material_category(category):
    return category == MATERIAL_CATEGORY


def normalize_cleanup(payload):
    require(isinstance(payload,dict) and payload.get("kind")=="bag_cleanup","가방정리 조건 형식 오류")
    name,category,keep=payload.get("name"),payload.get("category"),payload.get("keep")
    try:name_units=len(name.encode("utf-16-le"))//2 if isinstance(name,str) else 0
    except UnicodeEncodeError:name_units=0
    require(isinstance(name,str) and name.strip() and 1<=name_units<=50 and
            all(ord(c)>=32 and ord(c)!=127 for c in name),"정리할 재료 이름은 게임 검색 한도인 UTF-16 1~50자여야 합니다.")
    require(is_material_category(category),"가방정리는 재료 분류만 사용할 수 있습니다.")
    require(type(keep) is int and 0<=keep<=MAX_KEEP,"보존 수량은 0~2,147,483,647 정수로 입력해 주세요.")
    params=dict(kind="bag_cleanup",name=name,category=category,keep=keep)
    return params,f"가방정리 · {name} · {keep:,}개 보존"


def material_rows(rows, search=""):
    """Aggregate positions separately; never turn storage into sale quantity."""
    require(isinstance(search,str) and len(search)<=512,"검색어는 512자 이내로 입력해 주세요.")
    require(isinstance(rows,list),"재고 목록 형식 오류")
    term=search.strip().casefold()
    result={}
    for row in rows:
        require(isinstance(row,dict),"재고 행 형식 오류")
        if not is_material_category(row.get("Category")):continue
        name=row.get("DisplayName")
        count,location,locked=row.get("Count"),row.get("Location"),row.get("IsLocked")
        require(isinstance(name,str) and name.strip() and type(count) is int and count>=0 and
                location in LOCATIONS and type(locked) is bool,"재료 재고의 이름·위치·수량·잠금 정보를 확인하지 못했습니다.")
        if term and term not in name.casefold():continue
        key=(row["Category"],name)
        target=result.setdefault(key,dict(name=name,category=key[0],inventory=0,character_storage=0,
                                         account_storage=0,total=0,inventory_locked=0))
        target[location]+=count
        target["total"]+=count
        if location=="inventory" and locked:target["inventory_locked"]+=count
    return sorted(result.values(),key=lambda row:(row["name"],row["category"]))
