<?php

namespace App\Services\PwApi\Clone;

class CloneService extends \App\Services\PwApi\Core\PwApiService
{
    public function getRoleStatus($role): ?array
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleStatus'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        $result = $this->gamed->unmarshal($data, $this->data['role']['status']);

        return is_array($result) ? $result : null;
    }

    public function getRoleBase($role): ?array
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleBase'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        $result = $this->gamed->unmarshal($data, $this->data['role']['base']);

        return is_array($result) ? $result : null;
    }

    public function getRoleInventory($role)
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleInventory'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['role']['pocket']);
    }

    public function getRoleEquipment($role)
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleEquipment'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['role']['equipment']);
    }

    public function getRoleStorehouse($role)
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleStoreHouse'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['role']['storehouse']);
    }

    public function cloneRoleStatusProtocol($sourceRoleId, $targetRoleId)
    {
        $sourceStatus = $this->getRoleStatus($sourceRoleId);
        $targetStatus = $this->getRoleStatus($targetRoleId);

        if (! $sourceStatus || ! $targetStatus) {
            return ['success' => false, 'message' => 'Failed to get role status data'];
        }

        $excludedFields = ['exp', 'sp', 'pp', 'hp', 'mp'];

        foreach ($sourceStatus as $field => $value) {
            if (! in_array($field, $excludedFields)) {
                $targetStatus[$field] = $value;
            }
        }

        $pack = pack('N*', -1, $targetRoleId).$this->gamed->marshal($targetStatus, $this->data['role']['status']);
        $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRoleStatus'], $pack));

        sleep(1);

        $verifyStatus = $this->getRoleStatus($targetRoleId);

        $verified = true;
        foreach ($sourceStatus as $field => $value) {
            if (in_array($field, $excludedFields)) {
                continue;
            }
            if (! isset($verifyStatus[$field]) || $verifyStatus[$field] != $value) {
                $verified = false;
                break;
            }
        }

        if ($verified) {
            return [
                'success' => true,
                'message' => 'Status cloned successfully via protocol (EXP/SP/PP/HP/MP excluded)',
            ];
        }

        return ['success' => false, 'message' => 'Status clone verification failed - target may be online'];
    }

    public function cloneRoleBaseProtocol($sourceRoleId, $targetRoleId)
    {
        $sourceBase = $this->getRoleBase($sourceRoleId);
        $sourceStatus = $this->getRoleStatus($sourceRoleId);
        $targetBase = $this->getRoleBase($targetRoleId);
        $targetPocket = $this->getRoleInventory($targetRoleId) ?: [];
        $targetEquipment = $this->getRoleEquipment($targetRoleId) ?: [];
        $targetStorehouse = $this->getRoleStorehouse($targetRoleId) ?: [];
        $targetTask = $this->getRoleTask($targetRoleId) ?: [];

        if (! $sourceBase || ! $targetBase || ! $sourceStatus) {
            return ['success' => false, 'message' => 'Failed to get role data'];
        }

        $preserveFields = ['id', 'name', 'userid', 'delete_time', 'create_time', 'lastlogin_time', 'forbidcount', 'forbid', 'status'];

        foreach ($sourceBase as $field => $value) {
            if (! in_array($field, $preserveFields)) {
                $targetBase[$field] = $value;
            }
        }

        $fullRole = [
            'base' => $targetBase,
            'status' => $sourceStatus,
            'pocket' => $targetPocket,
            'equipment' => $targetEquipment,
            'storehouse' => $targetStorehouse,
            'task' => $targetTask,
        ];

        $this->putRole($targetRoleId, $fullRole);

        sleep(1);

        $verifyBase = $this->getRoleBase($targetRoleId);
        $verifyStatus = $this->getRoleStatus($targetRoleId);

        $verified = true;
        foreach ($sourceBase as $field => $value) {
            if (in_array($field, $preserveFields)) {
                continue;
            }
            if (! isset($verifyBase[$field]) || $verifyBase[$field] != $value) {
                $verified = false;
                break;
            }
        }

        if ($verified) {
            return [
                'success' => true,
                'message' => 'Base + Status cloned successfully (ID/Name/UserID preserved, all stats copied)',
            ];
        }

        return ['success' => false, 'message' => 'Clone verification failed - target may be online'];
    }

    public function copyRole($sourceRoleId, $targetRoleId)
    {
        $sourceStatus = $this->getRoleStatus($sourceRoleId);
        $sourcePocket = $this->getRoleInventory($sourceRoleId);
        $targetStatus = $this->getRoleStatus($targetRoleId);
        $targetPocket = $this->getRoleInventory($targetRoleId);

        if (! $sourceStatus || ! $targetStatus) {
            return ['success' => false, 'message' => 'Could not get role data'];
        }

        $packSource = pack('N*', -1, $sourceRoleId);
        $responseSource = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $packSource));
        $sourceRawData = $this->gamed->deleteHeader($responseSource);

        $packTarget = pack('N*', -1, $targetRoleId);
        $responseTarget = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $packTarget));
        $targetRawData = $this->gamed->deleteHeader($responseTarget);

        if (! $sourceRawData || strlen($sourceRawData) < 1000 || ! $targetRawData || strlen($targetRawData) < 1000) {
            return ['success' => false, 'message' => 'Failed to get raw role data'];
        }

        $sourcePosx = $sourceStatus['posx'] ?? 0;
        $sourcePosy = $sourceStatus['posy'] ?? 0;
        $sourcePosz = $sourceStatus['posz'] ?? 0;

        $targetPosx = $targetStatus['posx'] ?? 0;
        $targetPosy = $targetStatus['posy'] ?? 0;
        $targetPosz = $targetStatus['posz'] ?? 0;

        $sourcePosPattern = strrev(pack('f', $sourcePosx)).strrev(pack('f', $sourcePosy)).strrev(pack('f', $sourcePosz));
        $targetPosPattern = strrev(pack('f', $targetPosx)).strrev(pack('f', $targetPosy)).strrev(pack('f', $targetPosz));

        $sourcePosOffset = strpos($sourceRawData, $sourcePosPattern);
        $targetPosOffset = strpos($targetRawData, $targetPosPattern);

        if ($sourcePosOffset === false || $targetPosOffset === false) {
            return ['success' => false, 'message' => 'Could not find position data in raw bytes'];
        }

        $sourceStatusStart = $sourcePosOffset - 33;
        $targetStatusStart = $targetPosOffset - 33;

        $sourceIcapacity = $sourcePocket['icapacity'] ?? 32;
        $sourceTimestamp = $sourcePocket['timestamp'] ?? 0;
        $sourceMoney = $sourcePocket['money'] ?? 0;

        $targetIcapacity = $targetPocket['icapacity'] ?? 32;
        $targetTimestamp = $targetPocket['timestamp'] ?? 0;
        $targetMoney = $targetPocket['money'] ?? 0;

        $sourcePocketPattern = pack('NNN', $sourceIcapacity, $sourceTimestamp, $sourceMoney);
        $targetPocketPattern = pack('NNN', $targetIcapacity, $targetTimestamp, $targetMoney);

        $sourcePocketPos = strpos($sourceRawData, $sourcePocketPattern);
        $targetPocketPos = strpos($targetRawData, $targetPocketPattern);

        if ($sourcePocketPos === false || $targetPocketPos === false) {
            return ['success' => false, 'message' => 'Could not find pocket section in raw bytes'];
        }

        $sourceStatusSection = substr($sourceRawData, $sourceStatusStart, $sourcePocketPos - $sourceStatusStart);
        $sourcePocketAndAfter = substr($sourceRawData, $sourcePocketPos);

        $newTargetData = substr($targetRawData, 0, $targetStatusStart);
        $newTargetData .= $sourceStatusSection;
        $newTargetData .= $sourcePocketAndAfter;

        $putPack = pack('NNC', -1, $targetRoleId, 1).$newTargetData;
        $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRole'], $putPack));

        sleep(1);
        $verifyStatus = $this->getRoleStatus($targetRoleId);

        $sourceLevel = $sourceStatus['level'] ?? -1;
        $verifyLevel = $verifyStatus['level'] ?? 0;

        if ($verifyLevel == $sourceLevel) {
            return [
                'success' => true,
                'message' => 'Role copied successfully',
                'source_role' => $sourceRoleId,
                'target_role' => $targetRoleId,
                'copied_level' => $verifyLevel,
            ];
        }

        return ['success' => false, 'message' => 'Copy failed - target character might be online'];
    }

    public function cloneFullRole($sourceRoleId, $targetRoleId)
    {
        $packSource = pack('N*', -1, $sourceRoleId);
        $responseSource = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $packSource));
        $sourceData = $this->gamed->deleteHeader($responseSource);

        $packTarget = pack('N*', -1, $targetRoleId);
        $responseTarget = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $packTarget));
        $targetData = $this->gamed->deleteHeader($responseTarget);

        if (! $sourceData || strlen($sourceData) < 500 || ! $targetData || strlen($targetData) < 500) {
            return ['success' => false, 'message' => 'Failed to get role data'];
        }

        $targetBase = $this->getRoleBase($targetRoleId);
        $sourceBase = $this->getRoleBase($sourceRoleId);

        if (! $targetBase || ! $sourceBase) {
            return ['success' => false, 'message' => 'Failed to get role base data'];
        }

        $targetUserId = $targetBase['userid'] ?? 0;

        $targetNameStr = $targetBase['name'] ?? '';
        $targetNameBin = '';
        foreach (mb_str_split($targetNameStr) as $char) {
            $targetNameBin .= $char.chr(0);
        }
        $targetNameWithLen = $this->gamed->cuint(strlen($targetNameBin)).$targetNameBin;

        $sourceNameOffset = 5;
        $sourceNameLen = ord($sourceData[$sourceNameOffset]);
        $sourceNameCuintLen = $sourceNameLen >= 128 ? 2 : 1;
        if ($sourceNameLen >= 128) {
            $sourceNameLen = (($sourceNameLen & 0x7F) << 8) | ord($sourceData[$sourceNameOffset + 1]);
        }
        $sourceNameSectionLen = $sourceNameCuintLen + $sourceNameLen;

        $newData = substr($sourceData, 0, 1);
        $newData .= pack('N', $targetRoleId);
        $newData .= $targetNameWithLen;
        $newData .= substr($sourceData, 5 + $sourceNameSectionLen);

        $sourceUserIdPack = pack('N', $sourceBase['userid'] ?? 0);
        $targetUserIdPack = pack('N', $targetUserId);

        $searchStart = 100;
        $searchEnd = 600;
        $pos = strpos($newData, $sourceUserIdPack, $searchStart);

        if ($pos !== false && $pos < $searchEnd) {
            $newData = substr($newData, 0, $pos).$targetUserIdPack.substr($newData, $pos + 4);
        }

        $putPack = pack('NNC', -1, $targetRoleId, 1).$newData;
        $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRole'], $putPack));

        sleep(1);

        $verifyBase = $this->getRoleBase($targetRoleId);
        if ($verifyBase && $verifyBase['id'] == $targetRoleId && $verifyBase['userid'] == $targetUserId) {
            return [
                'success' => true,
                'message' => 'Full role clone completed successfully',
                'source' => $sourceBase['name'] ?? $sourceRoleId,
                'target' => $targetBase['name'] ?? $targetRoleId,
            ];
        }

        return ['success' => false, 'message' => 'Clone verification failed - target may be online'];
    }

    public function cloneSection($sourceRoleId, $targetRoleId, $section)
    {
        $packSource = pack('N*', -1, $sourceRoleId);
        $responseSource = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $packSource));
        $sourceRawData = $this->gamed->deleteHeader($responseSource);

        $packTarget = pack('N*', -1, $targetRoleId);
        $responseTarget = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $packTarget));
        $targetRawData = $this->gamed->deleteHeader($responseTarget);

        if (! $sourceRawData || strlen($sourceRawData) < 500 || ! $targetRawData || strlen($targetRawData) < 500) {
            return ['success' => false, 'message' => 'Failed to get raw role data'];
        }

        $sourceStatus = $this->getRoleStatus($sourceRoleId);
        $sourcePocket = $this->getRoleInventory($sourceRoleId);
        $sourceEquip = $this->getRoleEquipment($sourceRoleId);
        $sourceStore = $this->getRoleStorehouse($sourceRoleId);

        $targetStatus = $this->getRoleStatus($targetRoleId);
        $targetPocket = $this->getRoleInventory($targetRoleId);
        $targetEquip = $this->getRoleEquipment($targetRoleId);
        $targetStore = $this->getRoleStorehouse($targetRoleId);

        if (! $sourceStatus || ! $targetStatus) {
            return ['success' => false, 'message' => 'Could not get role status data'];
        }

        $sourcePosx = $sourceStatus['posx'] ?? 0;
        $sourcePosy = $sourceStatus['posy'] ?? 0;
        $sourcePosz = $sourceStatus['posz'] ?? 0;
        $targetPosx = $targetStatus['posx'] ?? 0;
        $targetPosy = $targetStatus['posy'] ?? 0;
        $targetPosz = $targetStatus['posz'] ?? 0;

        $sourcePosPattern = strrev(pack('f', $sourcePosx)).strrev(pack('f', $sourcePosy)).strrev(pack('f', $sourcePosz));
        $targetPosPattern = strrev(pack('f', $targetPosx)).strrev(pack('f', $targetPosy)).strrev(pack('f', $targetPosz));

        $sourcePosOffset = strpos($sourceRawData, $sourcePosPattern);
        $targetPosOffset = strpos($targetRawData, $targetPosPattern);

        if ($sourcePosOffset === false || $targetPosOffset === false) {
            return ['success' => false, 'message' => 'Could not find position data'];
        }

        $sourceStatusStart = $sourcePosOffset - 33;
        $targetStatusStart = $targetPosOffset - 33;

        $sourcePocketItems = $sourcePocket['inv'] ?? [];
        $targetPocketItems = $targetPocket['inv'] ?? [];

        $sourcePocketStart = null;
        $targetPocketStart = null;

        $sourcePocketMoney = $sourcePocket['money'] ?? 0;
        $targetPocketMoney = $targetPocket['money'] ?? 0;

        if (! empty($sourcePocketItems)) {
            $firstItem = reset($sourcePocketItems);
            $firstItemPattern = pack('NNNN', $firstItem['id'], $firstItem['pos'], $firstItem['count'], $firstItem['max_count'] ?? 1);
            $firstItemOffset = strpos($sourceRawData, $firstItemPattern, $sourceStatusStart + 500);
            if ($firstItemOffset !== false) {
                $itemCount = count($sourcePocketItems);
                $cuintLen = $itemCount < 128 ? 1 : 2;
                $sourcePocketStart = $firstItemOffset - $cuintLen - 4;
            }
        }
        if ($sourcePocketStart === null) {
            for ($i = $sourceStatusStart + 500; $i < strlen($sourceRawData) - 100; $i++) {
                $potentialMoney = unpack('N', substr($sourceRawData, $i, 4))[1] ?? 0;
                if ($potentialMoney == $sourcePocketMoney) {
                    $itemCount = ord($sourceRawData[$i + 4]);
                    if ($itemCount == count($sourcePocketItems)) {
                        $sourcePocketStart = $i;
                        break;
                    }
                }
            }
        }

        if (! empty($targetPocketItems)) {
            $firstItem = reset($targetPocketItems);
            $firstItemPattern = pack('NNNN', $firstItem['id'], $firstItem['pos'], $firstItem['count'], $firstItem['max_count'] ?? 1);
            $firstItemOffset = strpos($targetRawData, $firstItemPattern, $targetStatusStart + 500);
            if ($firstItemOffset !== false) {
                $itemCount = count($targetPocketItems);
                $cuintLen = $itemCount < 128 ? 1 : 2;
                $targetPocketStart = $firstItemOffset - $cuintLen - 4;
            }
        }
        if ($targetPocketStart === null) {
            for ($i = $targetStatusStart + 500; $i < strlen($targetRawData) - 100; $i++) {
                $potentialMoney = unpack('N', substr($targetRawData, $i, 4))[1] ?? 0;
                if ($potentialMoney == $targetPocketMoney) {
                    $itemCount = ord($targetRawData[$i + 4]);
                    if ($itemCount == count($targetPocketItems)) {
                        $targetPocketStart = $i;
                        break;
                    }
                }
            }
        }

        if ($sourcePocketStart === null || $targetPocketStart === null) {
            return ['success' => false, 'message' => 'Could not find pocket section'];
        }

        $newRoleData = null;

        if ($section === 'pocket') {
            $sourcePocketEnd = $this->findPocketEnd($sourceRawData, $sourcePocketStart, $sourcePocketItems);
            $targetPocketEnd = $this->findPocketEnd($targetRawData, $targetPocketStart, $targetPocketItems);

            if ($sourcePocketEnd === null || $targetPocketEnd === null) {
                return ['success' => false, 'message' => 'Could not find pocket section boundaries'];
            }

            $sourcePocketSection = substr($sourceRawData, $sourcePocketStart, $sourcePocketEnd - $sourcePocketStart);
            $newRoleData = substr($targetRawData, 0, $targetPocketStart).$sourcePocketSection.substr($targetRawData, $targetPocketEnd);

        } elseif ($section === 'equipment') {
            $sourcePocketEnd = $this->findPocketEnd($sourceRawData, $sourcePocketStart, $sourcePocketItems);
            $targetPocketEnd = $this->findPocketEnd($targetRawData, $targetPocketStart, $targetPocketItems);

            if ($sourcePocketEnd === null || $targetPocketEnd === null) {
                return ['success' => false, 'message' => 'Could not find pocket end'];
            }

            $sourceEquipStart = $sourcePocketEnd + 8;
            $targetEquipStart = $targetPocketEnd + 8;

            $sourceEquipEnd = $this->findEquipmentEnd($sourceRawData, $sourceEquipStart, $sourceEquip['eqp'] ?? []);
            $targetEquipEnd = $this->findEquipmentEnd($targetRawData, $targetEquipStart, $targetEquip['eqp'] ?? []);

            if ($sourceEquipEnd === null || $targetEquipEnd === null) {
                return ['success' => false, 'message' => 'Could not find equipment section boundaries'];
            }

            $sourceEquipSection = substr($sourceRawData, $sourceEquipStart, $sourceEquipEnd - $sourceEquipStart);
            $newRoleData = substr($targetRawData, 0, $targetEquipStart).$sourceEquipSection.substr($targetRawData, $targetEquipEnd);

        } elseif ($section === 'storehouse') {
            $sourceStoreCapacity = $sourceStore['capacity'] ?? 16;
            $sourceStoreMoney = $sourceStore['money'] ?? 0;
            $targetStoreCapacity = $targetStore['capacity'] ?? 16;
            $targetStoreMoney = $targetStore['money'] ?? 0;
            $sourceStoreItems = $sourceStore['store'] ?? [];
            $targetStoreItems = $targetStore['store'] ?? [];

            $sourceStoreStart = $this->findStorehouseStart($sourceRawData, $sourceStoreCapacity, $sourceStoreMoney, count($sourceStoreItems), $sourceStore);
            $targetStoreStart = $this->findStorehouseStart($targetRawData, $targetStoreCapacity, $targetStoreMoney, count($targetStoreItems), $targetStore);

            if ($sourceStoreStart === null || $targetStoreStart === null) {
                return ['success' => false, 'message' => 'Could not find storehouse section'];
            }

            $sourceStoreEnd = $this->findStorehouseEnd($sourceRawData, $sourceStoreStart, $sourceStore);
            $targetStoreEnd = $this->findStorehouseEnd($targetRawData, $targetStoreStart, $targetStore);

            $sourceStoreSection = substr($sourceRawData, $sourceStoreStart, $sourceStoreEnd - $sourceStoreStart);
            $newRoleData = substr($targetRawData, 0, $targetStoreStart).$sourceStoreSection.substr($targetRawData, $targetStoreEnd);
        }

        if ($newRoleData === null) {
            return ['success' => false, 'message' => 'Invalid section specified'];
        }

        $putPack = pack('NNC', -1, $targetRoleId, 1).$newRoleData;
        $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRole'], $putPack));

        sleep(1);

        return ['success' => true, 'message' => "Section '$section' cloned successfully"];
    }

    public function tradeInventory($roleId1, $roleId2)
    {
        $pack1 = pack('N*', -1, $roleId1);
        $response1 = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $pack1));
        $rawData1 = $this->gamed->deleteHeader($response1);

        $pack2 = pack('N*', -1, $roleId2);
        $response2 = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $pack2));
        $rawData2 = $this->gamed->deleteHeader($response2);

        if (! $rawData1 || strlen($rawData1) < 500 || ! $rawData2 || strlen($rawData2) < 500) {
            return ['success' => false, 'message' => 'Failed to get raw role data'];
        }

        $pocket1 = $this->getRoleInventory($roleId1);
        $pocket2 = $this->getRoleInventory($roleId2);
        $inv1Count = count($pocket1['inv'] ?? []);
        $inv2Count = count($pocket2['inv'] ?? []);

        $pocketStart1 = null;
        $pocketStart2 = null;

        if (! empty($pocket1['inv'])) {
            $firstItem = reset($pocket1['inv']);
            $itemPattern = pack('NNNN', $firstItem['id'], $firstItem['pos'], $firstItem['count'], $firstItem['max_count'] ?? 1);
            $itemOffset = strpos($rawData1, $itemPattern);
            if ($itemOffset !== false) {
                $cuintLen = $inv1Count < 128 ? 1 : 2;
                $pocketStart1 = $itemOffset - $cuintLen - 12;
            }
        }

        if (! empty($pocket2['inv'])) {
            $firstItem = reset($pocket2['inv']);
            $itemPattern = pack('NNNN', $firstItem['id'], $firstItem['pos'], $firstItem['count'], $firstItem['max_count'] ?? 1);
            $itemOffset = strpos($rawData2, $itemPattern);
            if ($itemOffset !== false) {
                $cuintLen = $inv2Count < 128 ? 1 : 2;
                $pocketStart2 = $itemOffset - $cuintLen - 12;
            }
        }

        if ($pocketStart1 === null || $pocketStart2 === null) {
            return ['success' => false, 'message' => 'Could not find pocket section'];
        }

        $pocketEnd1 = $this->calculatePocketEnd($rawData1, $pocketStart1);
        $pocketEnd2 = $this->calculatePocketEnd($rawData2, $pocketStart2);

        if ($pocketEnd1 === null || $pocketEnd2 === null) {
            return ['success' => false, 'message' => 'Could not find pocket boundaries'];
        }

        $pocketSection1 = substr($rawData1, $pocketStart1, $pocketEnd1 - $pocketStart1);
        $pocketSection2 = substr($rawData2, $pocketStart2, $pocketEnd2 - $pocketStart2);

        $newRawData1 = substr($rawData1, 0, $pocketStart1).$pocketSection2.substr($rawData1, $pocketEnd1);
        $newRawData2 = substr($rawData2, 0, $pocketStart2).$pocketSection1.substr($rawData2, $pocketEnd2);

        $putPack1 = pack('NNC', -1, $roleId1, 1).$newRawData1;
        $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRole'], $putPack1));

        $putPack2 = pack('NNC', -1, $roleId2, 1).$newRawData2;
        $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRole'], $putPack2));

        return [
            'success' => true,
            'message' => 'Trade inventory successful',
            'roleid1' => $roleId1,
            'roleid2' => $roleId2,
            'items_swapped' => [
                'role1_received' => $inv2Count,
                'role2_received' => $inv1Count,
            ],
        ];
    }

    public function calculatePocketEnd($roleData, $pocketStart)
    {
        $dataLen = strlen($roleData);

        if ($pocketStart === null || $pocketStart < 0 || $pocketStart >= $dataLen) {
            return null;
        }

        $offset = $pocketStart + 12;

        if ($offset >= $dataLen) {
            return null;
        }

        $itemCount = ord($roleData[$offset]);
        if ($itemCount >= 128) {
            if ($offset + 1 >= $dataLen) {
                return null;
            }
            $itemCount = (($itemCount & 0x7F) << 8) | ord($roleData[$offset + 1]);
            $offset += 2;
        } else {
            $offset += 1;
        }

        for ($i = 0; $i < $itemCount; $i++) {
            $offset += 16;

            if ($offset >= $dataLen) {
                return null;
            }

            $octetLen = ord($roleData[$offset]);
            if ($octetLen >= 128) {
                if ($offset + 1 >= $dataLen) {
                    return null;
                }
                $octetLen = (($octetLen & 0x7F) << 8) | ord($roleData[$offset + 1]);
                $offset += 2 + $octetLen;
            } else {
                $offset += 1 + $octetLen;
            }

            $offset += 20;

            if ($offset > $dataLen) {
                return null;
            }
        }

        $offset += 8;

        return $offset;
    }

    public function tradeSelectedItems($roleId1, $roleId2, $items1 = [], $items2 = [])
    {
        $pocket1 = $this->getRoleInventory($roleId1);
        $pocket2 = $this->getRoleInventory($roleId2);

        if (! $pocket1 || ! $pocket2) {
            return ['success' => false, 'message' => 'Failed to get inventory data'];
        }

        $inv1 = $pocket1['inv'] ?? [];
        $inv2 = $pocket2['inv'] ?? [];

        $itemsToRole2 = [];
        $remainingInv1 = [];
        foreach ($inv1 as $item) {
            if (in_array($item['id'], $items1)) {
                $itemsToRole2[] = $item;
                $key = array_search($item['id'], $items1);
                if ($key !== false) {
                    unset($items1[$key]);
                }
            } else {
                $remainingInv1[] = $item;
            }
        }

        $itemsToRole1 = [];
        $remainingInv2 = [];
        foreach ($inv2 as $item) {
            if (in_array($item['id'], $items2)) {
                $itemsToRole1[] = $item;
                $key = array_search($item['id'], $items2);
                if ($key !== false) {
                    unset($items2[$key]);
                }
            } else {
                $remainingInv2[] = $item;
            }
        }

        $newInv1 = array_merge($remainingInv1, $itemsToRole1);
        $newInv2 = array_merge($remainingInv2, $itemsToRole2);

        $pos = 0;
        foreach ($newInv1 as &$item) {
            $item['pos'] = $pos++;
        }
        $pos = 0;
        foreach ($newInv2 as &$item) {
            $item['pos'] = $pos++;
        }

        $newPocket1 = $pocket1;
        $newPocket1['inv'] = $newInv1;

        $newPocket2 = $pocket2;
        $newPocket2['inv'] = $newInv2;

        $pack1 = pack('N*', -1, $roleId1);
        $response1 = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $pack1));
        $rawData1 = $this->gamed->deleteHeader($response1);

        $pack2 = pack('N*', -1, $roleId2);
        $response2 = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRole'], $pack2));
        $rawData2 = $this->gamed->deleteHeader($response2);

        if (! $rawData1 || ! $rawData2) {
            return ['success' => false, 'message' => 'Failed to get raw role data'];
        }

        $pocketStart1 = $this->findPocketStart($rawData1, $inv1, $pocket1);
        $pocketEnd1 = $pocketStart1 !== null ? $this->calculatePocketEnd($rawData1, $pocketStart1) : null;

        $pocketStart2 = $this->findPocketStart($rawData2, $inv2, $pocket2);
        $pocketEnd2 = $pocketStart2 !== null ? $this->calculatePocketEnd($rawData2, $pocketStart2) : null;

        if ($pocketStart1 === null || $pocketEnd1 === null || $pocketStart2 === null || $pocketEnd2 === null) {
            return ['success' => false, 'message' => 'Could not find pocket section boundaries'];
        }

        if ($pocketEnd1 > strlen($rawData1) || $pocketEnd2 > strlen($rawData2)) {
            return ['success' => false, 'message' => 'Invalid pocket boundaries detected'];
        }

        $newPocketBytes1 = $this->gamed->marshal($newPocket1, $this->data['role']['pocket']);
        $newPocketBytes2 = $this->gamed->marshal($newPocket2, $this->data['role']['pocket']);

        if (! $newPocketBytes1 || ! $newPocketBytes2) {
            return ['success' => false, 'message' => 'Failed to marshal new pocket data'];
        }

        $newRawData1 = substr($rawData1, 0, $pocketStart1).$newPocketBytes1.substr($rawData1, $pocketEnd1);
        $newRawData2 = substr($rawData2, 0, $pocketStart2).$newPocketBytes2.substr($rawData2, $pocketEnd2);

        $putPack1 = pack('NNC', -1, $roleId1, 1).$newRawData1;
        $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRole'], $putPack1));

        $putPack2 = pack('NNC', -1, $roleId2, 1).$newRawData2;
        $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRole'], $putPack2));

        return [
            'success' => true,
            'message' => 'Items traded successfully',
            'roleId1' => $roleId1,
            'roleId2' => $roleId2,
            'itemsToRole2' => count($itemsToRole2),
            'itemsToRole1' => count($itemsToRole1),
        ];
    }

    public function findPocketStart($rawData, $items, $pocket = null)
    {
        if (! $rawData) {
            return null;
        }

        if (! empty($items)) {
            $firstItem = reset($items);
            if (isset($firstItem['id']) && isset($firstItem['pos']) && isset($firstItem['count'])) {
                $itemPattern = pack('NNNN', $firstItem['id'], $firstItem['pos'], $firstItem['count'], $firstItem['max_count'] ?? 1);
                $itemOffset = strpos($rawData, $itemPattern);

                if ($itemOffset !== false) {
                    $itemCount = count($items);
                    $cuintLen = $itemCount < 128 ? 1 : 2;
                    $pocketStart = $itemOffset - $cuintLen - 12;

                    if ($pocketStart >= 0) {
                        return $pocketStart;
                    }
                }
            }
        }

        if ($pocket !== null && isset($pocket['icapacity']) && isset($pocket['money'])) {
            $icapacity = (int) ($pocket['icapacity'] ?? 128);
            $money = (int) ($pocket['money'] ?? 0);

            $icapacities = [$icapacity, 32, 64, 128];
            foreach ($icapacities as $cap) {
                $moneyPattern = pack('N', $money)."\x00";
                $searchOffset = 0;

                while (($pos = strpos($rawData, $moneyPattern, $searchOffset)) !== false) {
                    $pocketStart = $pos - 8;
                    if ($pocketStart >= 0) {
                        $capCheck = unpack('N', substr($rawData, $pocketStart, 4))[1];
                        if ($capCheck == $cap) {
                            return $pocketStart;
                        }
                    }
                    $searchOffset = $pos + 1;
                }
            }
        }

        return null;
    }

    public function findEquipmentEnd($roleData, $equipStart, $items)
    {
        $offset = $equipStart;

        $itemCount = ord($roleData[$offset]);
        if ($itemCount >= 128) {
            $itemCount = (($itemCount & 0x7F) << 8) | ord($roleData[$offset + 1]);
            $offset += 2;
        } else {
            $offset += 1;
        }

        for ($i = 0; $i < $itemCount; $i++) {
            $offset += 16;

            $dataLen = ord($roleData[$offset]);
            if ($dataLen >= 128) {
                $dataLen = (($dataLen & 0x7F) << 8) | ord($roleData[$offset + 1]);
                $offset += 2 + $dataLen;
            } else {
                $offset += 1 + $dataLen;
            }

            $offset += 20;
        }

        return $offset;
    }

    public function findStorehouseStart($roleData, $capacity, $money, $itemCount, $storeData)
    {
        $pattern = pack('NN', $capacity, $money);
        $size1 = $storeData['size1'] ?? 0;
        $size2 = $storeData['size2'] ?? 0;
        $size3 = $storeData['size3'] ?? 0;
        $dress = $storeData['dress'] ?? [];
        $material = $storeData['material'] ?? [];

        $offset = 0;
        while (($pos = strpos($roleData, $pattern, $offset)) !== false) {
            $countOffset = $pos + 8;
            if ($countOffset >= strlen($roleData)) {
                $offset = $pos + 1;

                continue;
            }

            $count = ord($roleData[$countOffset]);
            if ($count >= 128) {
                if ($countOffset + 1 >= strlen($roleData)) {
                    $offset = $pos + 1;

                    continue;
                }
                $count = (($count & 0x7F) << 8) | ord($roleData[$countOffset + 1]);
            }

            if ($count == $itemCount) {
                $endOffset = $this->findStorehouseEnd($roleData, $pos, $storeData);

                if ($endOffset > $pos && $endOffset <= strlen($roleData)) {
                    $size3Pos = $endOffset - 1;
                    if ($size3Pos > 0 && $size3Pos < strlen($roleData)) {
                        $checkSize3 = ord($roleData[$size3Pos]);

                        $sectionSize = $endOffset - $pos;
                        if ($sectionSize >= 12 && $sectionSize <= 10000) {
                            if ($checkSize3 == $size3 || ($size3 == 0 && $checkSize3 < 200)) {
                                return $pos;
                            }
                        }
                    }
                }
            }
            $offset = $pos + 1;
        }

        if ($money == 0) {
            $capPattern = pack('N', $capacity);
            $offset = 0;
            while (($pos = strpos($roleData, $capPattern, $offset)) !== false) {
                if ($pos + 8 < strlen($roleData)) {
                    $countAtPos = ord($roleData[$pos + 8]);
                    if ($countAtPos >= 128) {
                        $countAtPos = (($countAtPos & 0x7F) << 8) | ord($roleData[$pos + 9]);
                    }

                    if ($countAtPos == $itemCount) {
                        $endOffset = $this->findStorehouseEnd($roleData, $pos, $storeData);
                        if ($endOffset > $pos) {
                            return $pos;
                        }
                    }
                }
                $offset = $pos + 1;
            }
        }

        return null;
    }

    public function findStorehouseEnd($roleData, $storeStart, $storeData)
    {
        $dataLen_total = strlen($roleData);
        $offset = $storeStart + 8;

        if ($offset >= $dataLen_total) {
            return $storeStart + 8;
        }

        $itemCount = ord($roleData[$offset]);
        if ($itemCount >= 128) {
            if ($offset + 1 >= $dataLen_total) {
                return $offset + 1;
            }
            $itemCount = (($itemCount & 0x7F) << 8) | ord($roleData[$offset + 1]);
            $offset += 2;
        } else {
            $offset += 1;
        }

        for ($i = 0; $i < $itemCount && $i < 1000; $i++) {
            if ($offset + 16 > $dataLen_total) {
                break;
            }
            $offset += 16;

            if ($offset >= $dataLen_total) {
                break;
            }

            $dataLen = ord($roleData[$offset]);
            if ($dataLen >= 128) {
                if ($offset + 1 >= $dataLen_total) {
                    $offset += 2;
                    break;
                }
                $dataLen = (($dataLen & 0x7F) << 8) | ord($roleData[$offset + 1]);
                $offset += 2 + $dataLen;
            } else {
                $offset += 1 + $dataLen;
            }

            if ($offset + 20 > $dataLen_total) {
                break;
            }
            $offset += 20;
        }

        if ($offset + 2 > $dataLen_total) {
            return $offset;
        }
        $offset += 2;

        if ($offset >= $dataLen_total) {
            return $offset;
        }

        $dressCount = ord($roleData[$offset]);
        if ($dressCount >= 128) {
            if ($offset + 1 >= $dataLen_total) {
                return $offset + 2;
            }
            $dressCount = (($dressCount & 0x7F) << 8) | ord($roleData[$offset + 1]);
            $offset += 2;
        } else {
            $offset += 1;
        }

        for ($i = 0; $i < $dressCount && $i < 1000; $i++) {
            if ($offset + 16 > $dataLen_total) {
                break;
            }
            $offset += 16;

            if ($offset >= $dataLen_total) {
                break;
            }

            $dataLen = ord($roleData[$offset]);
            if ($dataLen >= 128) {
                if ($offset + 1 >= $dataLen_total) {
                    $offset += 2;
                    break;
                }
                $dataLen = (($dataLen & 0x7F) << 8) | ord($roleData[$offset + 1]);
                $offset += 2 + $dataLen;
            } else {
                $offset += 1 + $dataLen;
            }

            if ($offset + 20 > $dataLen_total) {
                break;
            }
            $offset += 20;
        }

        if ($offset >= $dataLen_total) {
            return $offset;
        }

        $matCount = ord($roleData[$offset]);
        if ($matCount >= 128) {
            if ($offset + 1 >= $dataLen_total) {
                return $offset + 2;
            }
            $matCount = (($matCount & 0x7F) << 8) | ord($roleData[$offset + 1]);
            $offset += 2;
        } else {
            $offset += 1;
        }

        for ($i = 0; $i < $matCount && $i < 1000; $i++) {
            if ($offset + 16 > $dataLen_total) {
                break;
            }
            $offset += 16;

            if ($offset >= $dataLen_total) {
                break;
            }

            $dataLen = ord($roleData[$offset]);
            if ($dataLen >= 128) {
                if ($offset + 1 >= $dataLen_total) {
                    $offset += 2;
                    break;
                }
                $dataLen = (($dataLen & 0x7F) << 8) | ord($roleData[$offset + 1]);
                $offset += 2 + $dataLen;
            } else {
                $offset += 1 + $dataLen;
            }

            if ($offset + 20 > $dataLen_total) {
                break;
            }
            $offset += 20;
        }

        if ($offset + 1 > $dataLen_total) {
            return $offset;
        }
        $offset += 1;

        return $offset;
    }

    private function findPocketEnd($roleData, $pocketStart, $items)
    {
        $offset = $pocketStart + 4;

        $itemCount = ord($roleData[$offset]);
        if ($itemCount >= 128) {
            $itemCount = (($itemCount & 0x7F) << 8) | ord($roleData[$offset + 1]);
            $offset += 2;
        } else {
            $offset += 1;
        }

        for ($i = 0; $i < $itemCount; $i++) {
            $offset += 16;

            $dataLen = ord($roleData[$offset]);
            if ($dataLen >= 128) {
                $dataLen = (($dataLen & 0x7F) << 8) | ord($roleData[$offset + 1]);
                $offset += 2 + $dataLen;
            } else {
                $offset += 1 + $dataLen;
            }

            $offset += 20;
        }

        return $offset;
    }
}
