<?php
/**
 * MU Nexora — public, read-only decoder for equipped items.
 *
 * The current Season 6/X-Team database uses DbVersion 3 item blocks (16 bytes).
 * Unsupported formats fail closed instead of guessing.
 */
class NexoraItemDecoder {

    const DB_VERSION_3_ITEM_BYTES = 16;
    const EQUIPMENT_SLOTS = 12;

    public static function slotDefinitions() {
        return array(
            0  => array('key' => 'weapon1', 'label' => 'Arma derecha'),
            1  => array('key' => 'weapon2', 'label' => 'Arma izquierda / Escudo'),
            2  => array('key' => 'helm',    'label' => 'Casco'),
            3  => array('key' => 'armor',   'label' => 'Armadura'),
            4  => array('key' => 'pants',   'label' => 'Pantalones'),
            5  => array('key' => 'gloves',  'label' => 'Guantes'),
            6  => array('key' => 'boots',   'label' => 'Botas'),
            7  => array('key' => 'wings',   'label' => 'Alas'),
            8  => array('key' => 'pet',     'label' => 'Pet'),
            9  => array('key' => 'pendant', 'label' => 'Pendant'),
            10 => array('key' => 'ring1',   'label' => 'Anillo 1'),
            11 => array('key' => 'ring2',   'label' => 'Anillo 2'),
        );
    }

    public static function decodeEquipment($inventoryHex, $dbVersion, $inventoryBytes=null) {
        $inventoryHex = strtoupper(preg_replace('/[^0-9A-F]/i', '', (string)$inventoryHex));
        $dbVersion = (int)$dbVersion;
        $inventoryBytes = $inventoryBytes === null ? (int)(strlen($inventoryHex) / 2) : (int)$inventoryBytes;

        if($inventoryHex === '') {
            return array(
                'supported' => true,
                'format' => 'empty',
                'inventory_bytes' => $inventoryBytes,
                'items' => self::emptySlots(),
            );
        }

        if($dbVersion !== 3) {
            return array(
                'supported' => false,
                'format' => 'dbversion-'.$dbVersion,
                'inventory_bytes' => $inventoryBytes,
                'items' => self::emptySlots(),
                'warning' => 'El formato de inventario de este personaje todavía no está soportado de forma segura.',
            );
        }

        $itemHexLength = self::DB_VERSION_3_ITEM_BYTES * 2;
        $requiredHexLength = self::EQUIPMENT_SLOTS * $itemHexLength;
        if(strlen($inventoryHex) < $requiredHexLength) {
            return array(
                'supported' => false,
                'format' => 'dbversion-3-truncated',
                'inventory_bytes' => $inventoryBytes,
                'items' => self::emptySlots(),
                'warning' => 'El inventario guardado no contiene suficientes bytes para leer los slots equipados.',
            );
        }

        $items = array();
        foreach(self::slotDefinitions() as $slotIndex => $slot) {
            $chunk = substr($inventoryHex, $slotIndex * $itemHexLength, $itemHexLength);
            $items[] = self::decodeV3Item($chunk, $slotIndex, $slot);
        }

        return array(
            'supported' => true,
            'format' => 'dbversion-3',
            'inventory_bytes' => $inventoryBytes,
            'items' => $items,
        );
    }

    private static function emptySlots() {
        $items = array();
        foreach(self::slotDefinitions() as $slotIndex => $slot) {
            $items[] = self::emptySlot($slotIndex, $slot);
        }
        return $items;
    }

    private static function emptySlot($slotIndex, $slot) {
        return array(
            'slot_index' => (int)$slotIndex,
            'slot' => $slot['key'],
            'slot_label' => $slot['label'],
            'occupied' => false,
        );
    }

    private static function decodeV3Item($hex, $slotIndex, $slot) {
        $hex = strtoupper((string)$hex);
        if(strlen($hex) !== 32 || preg_match('/^[F]+$/', $hex)) {
            return self::emptySlot($slotIndex, $slot);
        }

        $bytes = array();
        for($i=0; $i<16; $i++) {
            $pair = substr($hex, $i * 2, 2);
            if(strlen($pair) !== 2 || !ctype_xdigit($pair)) {
                return self::emptySlot($slotIndex, $slot);
            }
            $bytes[$i] = hexdec($pair);
        }

        // DbVersion 3 / Season 6 item layout.
        $indexLow = $bytes[0];
        $flags = $bytes[1];
        $durability = $bytes[2];
        $excellentByte = $bytes[7];
        $ancientByte = $bytes[8];
        $sectionOptionByte = $bytes[9];
        $harmonyByte = $bytes[10];

        // FF-filled records are empty. Keep a second conservative guard for malformed empty records.
        if($indexLow === 0xFF && $flags === 0xFF && $durability === 0xFF) {
            return self::emptySlot($slotIndex, $slot);
        }

        $index = $indexLow | (($excellentByte & 0x80) << 1);
        $section = ($sectionOptionByte >> 4) & 0x0F;
        $level = ($flags >> 3) & 0x0F;
        $skill = (bool)($flags & 0x80);
        $luck = (bool)($flags & 0x04);
        $additionalRaw = ($flags & 0x03) + (($excellentByte & 0x40) ? 4 : 0);
        $excellentMask = $excellentByte & 0x3F;
        $excellentCount = self::countBits($excellentMask);
        $ancientRaw = $ancientByte & 0x0F;
        $option380 = (bool)($sectionOptionByte & 0x08);
        $refineRaw = $sectionOptionByte & 0x07;

        $socketBytes = array_slice($bytes, 11, 5);
        $socketAvailable = 0;
        $socketSeeded = 0;
        $hasSocketData = false;
        foreach($socketBytes as $socketValue) {
            if($socketValue === 0xFE) {
                $socketAvailable++;
                $hasSocketData = true;
            } elseif($socketValue !== 0xFF) {
                $socketSeeded++;
                $hasSocketData = true;
            }
        }

        // Byte 10 is shared: Harmony on regular items, Socket Bonus on socket items.
        // Socket payload in bytes 11-15 is the conservative discriminator available in the item block itself.
        $harmony = null;
        $socketBonusRaw = 0;
        if($hasSocketData) {
            $socketBonusRaw = ($harmonyByte === 0xFF) ? 0 : $harmonyByte;
        } elseif($harmonyByte !== 0x00 && $harmonyByte !== 0xFF) {
            $harmony = array(
                'type' => ($harmonyByte >> 4) & 0x0F,
                'level' => $harmonyByte & 0x0F,
            );
        }

        return array(
            'slot_index' => (int)$slotIndex,
            'slot' => $slot['key'],
            'slot_label' => $slot['label'],
            'occupied' => true,
            'section' => $section,
            'index' => $index,
            'code' => $section.':'.$index,
            'level' => $level,
            'skill' => $skill,
            'luck' => $luck,
            'additional_option_raw' => $additionalRaw,
            'durability' => $durability,
            'excellent' => $excellentCount > 0,
            'excellent_count' => $excellentCount,
            'ancient' => $ancientRaw > 0,
            'ancient_raw' => $ancientRaw,
            'refine_raw' => $refineRaw,
            'option_380' => $option380,
            'harmony' => $harmony,
            'socket_bonus_raw' => $socketBonusRaw,
            'socket_values' => $socketBytes,
            'sockets_available' => $socketAvailable,
            'sockets_seeded' => $socketSeeded,
        );
    }

    private static function countBits($value) {
        $value = (int)$value;
        $count = 0;
        while($value > 0) {
            $count += $value & 1;
            $value >>= 1;
        }
        return $count;
    }
}
