<?php

namespace App\Services\PwHexParser;

use App\Services\PwHexParser\Types\Unequippable;

class AutoDetector
{
    private Decoder $decoder;

    public function __construct(Decoder $decoder = null)
    {
        $this->decoder = $decoder ?? new Decoder();
    }

    public function detect(string $hexString, ?string $flag = null): array
    {
        $candidates = [];
        
        // Get all available types from the Types directory
        $typeFiles = glob(__DIR__ . '/Types/*.php');
        $uniqueTypes = [];
        
        foreach ($typeFiles as $file) {
            $typeName = basename($file, '.php');
            if ($typeName !== 'Unequippable') {
                $uniqueTypes[] = $typeName;
            }
        }

        // Prioritize or filter by flag if provided
        $flagType = null;
        if ($flag) {
            // Map common flags/masks/names to Type classes
            $flagType = $this->getTypeFromFlag($flag);
            if ($flagType && in_array($flagType, $uniqueTypes)) {
                // Move prioritized type to front
                $uniqueTypes = array_diff($uniqueTypes, [$flagType]);
                array_unshift($uniqueTypes, $flagType);
            }
        }

        foreach ($uniqueTypes as $type) {
            $this->decoder->reset();
            $this->decoder->setHexString($hexString);
            
            try {
                $this->decoder->setItemType($type);
                
                $currentItemType = $this->decoder->getItemType();
                if ($currentItemType instanceof Unequippable) {
                    continue; 
                }

                $result = $this->decoder->decode();
                $finalPos = $this->decoder->getPosition();
                $totalLen = strlen($hexString);
                
                if (!isset($result['error'])) {
                    $sanityScore = $this->calculateSanityScore($result);
                    $maskScore = 0;
                    $maskValue = null;
                    $maskFlags = [];
                    
                    // Check item_type against Mask if present
                    if (isset($result['item_type'])) {
                        $itemTypeValue = $result['item_type'];
                        // In some cases item_type might be translated or object, but usually int
                        if (is_array($itemTypeValue) && isset($itemTypeValue['value'])) {
                             $itemTypeValue = $itemTypeValue['value'];
                        }
                        
                        if (is_numeric($itemTypeValue)) {
                            $maskValue = (int)$itemTypeValue;
                            $maskScore = $this->validateMask($maskValue, $type);
                            $maskFlags = Mask::decode($maskValue);
                        }
                    }
                    
                    // Bonus for mask match
                    if ($maskScore > 0) {
                        $sanityScore += $maskScore;
                    }
                    
                    // Bonus for explicit flag match (highest priority)
                    if ($flagType && $type === $flagType) {
                        $sanityScore += 10000; // Very high bonus for explicit flag match
                    }
                    
                    // Candidate Info
                    $candidateInfo = [
                        'type' => $type,
                        'match_type' => 'partial', // Default
                        'parsed_len' => $finalPos,
                        'total_len' => $totalLen,
                        'data' => $result,
                        'sanity_score' => $sanityScore,
                        'mask_value' => $maskValue,
                        'mask_flags' => $maskFlags
                    ];
                    
                    // Exact match
                    if ($finalPos === $totalLen) {
                        $candidateInfo['match_type'] = 'exact';
                        $candidates[] = $candidateInfo;
                    } else {
                        // Partial match
                        $candidates[] = $candidateInfo;
                    }
                } else {
                    // Error match
                    $candidates[] = [
                        'type' => $type,
                        'match_type' => 'error',
                        'error' => $result['error'],
                        'parsed_len' => $finalPos,
                        'total_len' => $totalLen,
                        'sanity_score' => -999
                    ];
                }
            } catch (\Throwable $e) {
                // Critical error
            }
        }
        
        // Sort candidates: Sanity > Exact > Closest Length
        usort($candidates, function($a, $b) {
            // Sanity Check (Higher is better)
            if (($a['sanity_score'] ?? 0) !== ($b['sanity_score'] ?? 0)) {
                return ($b['sanity_score'] ?? 0) - ($a['sanity_score'] ?? 0);
            }
            
            if ($a['match_type'] === 'exact' && $b['match_type'] !== 'exact') return -1;
            if ($b['match_type'] === 'exact' && $a['match_type'] !== 'exact') return 1;
            
            $diffA = abs(($a['parsed_len'] ?? 0) - ($a['total_len'] ?? 0));
            $diffB = abs(($b['parsed_len'] ?? 0) - ($b['total_len'] ?? 0));
            
            return $diffA - $diffB;
        });

        return $candidates;
    }
    
    private function getTypeFromFlag(string $flag): ?string
    {
        // Map masks or proctypes to Class names
        $flagLower = strtolower($flag);
        
        // String matches
        if (strpos($flagLower, 'weapon') !== false) return 'Weapon';
        if (strpos($flagLower, 'armor') !== false) return 'Armor';
        if (strpos($flagLower, 'jewelry') !== false || strpos($flagLower, 'accessory') !== false) return 'Jewelry';
        if (strpos($flagLower, 'fashion') !== false) return 'Fashion';
        if (strpos($flagLower, 'flight') !== false || strpos($flagLower, 'fly') !== false) return 'Flight';
        if (strpos($flagLower, 'genie') !== false) return 'Genie';
        
        // Numeric masks handling
        if (is_numeric($flag)) {
            $val = (int)$flag;
            
            // Weapon: 1
            if (($val & 1) !== 0) return 'Weapon';
            
            // Armor: 2 (Helm), 16 (Chest), 64 (Leg), 128 (Feet), 256 (Arms), 8 (Robe)
            // Sum: 474
            if (($val & 474) !== 0) return 'Armor';
            
            // Jewelry: 1536 (Ring), 4 (Necklace), 32 (Belt)
            // Sum: 1572
            if (($val & 1572) !== 0) return 'Jewelry';
            
            // Fashion: 8192 (Chest), 16384 (Leg), 32768 (Feet), 65536 (Arms)
            // Sum: 122880
            if (($val & 122880) !== 0) return 'Fashion';
            
            // Flight: 4096
            if (($val & 4096) !== 0) return 'Flight';
            
            // Tome: 262144
            // Smiley: 524288
            // Charms: 1048576 (HP), 2097152 (MP)
        }
        
        return null;
    }

    
    private function validateMask(int $maskValue, string $currentType): int
    {
        try {
            // Get the Equipment Type NAME for this mask value
            // E.g. 36 is not a single bitmask, but might be composite?
            // The original code `getEquipmentTypeFromMask` expects single bit match primarily or returns first match.
            // If maskValue is 36 (0x24), 0x20 (WAIST) is matched.
            // Mask::getEquipmentTypeFromMask(36) -> 'Armor'.
            
            $detectedType = Mask::getEquipmentTypeFromMask($maskValue);
            
            if ($detectedType === $currentType) {
                return 5000; // Very high confidence bonus
            }
        } catch (\Throwable $e) {
            // Mask validation failed or unknown mask
        }
        return 0;
    }

    private function calculateSanityScore(array $data): int
    {
        $score = 100;
        
        // Check suspicious counts
        $countFields = ['sockets_count', 'addons_count', 'skills_count', 'equipments_count'];
        foreach ($countFields as $field) {
            if (isset($data[$field]) && is_numeric($data[$field])) {
                if ($data[$field] > 100) {
                    $score -= 1000; // Heavy penalty for impossible counts
                }
            }
        }
        
        // Check suspicious level (too high)
        if (isset($data['level']) && is_numeric($data['level'])) {
             if ($data['level'] > 500) {
                 $score -= 100;
             }
        }
        
        // Check suspicious durability (max < min)
        if (isset($data['min_durability']) && isset($data['max_durability'])) {
            if ($data['min_durability'] > $data['max_durability']) {
                $score -= 50;
            }
        }

        return $score;
    }
}
