merek) ? $this->merek : ''; $name = is_string($this->nama) ? $this->nama : ''; $sourceName = Str::startsWith(Str::lower($name), Str::lower($brand)) ? $name : trim($brand.' '.$name); $fullCompactVariants = self::compactVariants($sourceName); $modelCompactVariants = self::compactVariants($name); $brandVariants = self::brandVariants($brand); $bestMatch = null; $bestScore = 0; foreach ($images as $image) { $score = self::scoreImageMatch($image, $fullCompactVariants, $modelCompactVariants, $brandVariants); if ($score > $bestScore) { $bestScore = $score; $bestMatch = $image['path']; } } return $bestScore >= 70 ? $bestMatch : null; } private static function availableImages(): array { if (self::$cachedImagePaths !== null) { return self::$cachedImagePaths; } $directory = public_path(self::IMAGE_DIRECTORY); if (! File::isDirectory($directory)) { return self::$cachedImagePaths = []; } return self::$cachedImagePaths = collect(File::files($directory)) ->filter(fn ($file) => in_array(strtolower($file->getExtension()), ['jpg', 'jpeg', 'png', 'webp'], true)) ->map(function ($file): array { return [ 'path' => self::IMAGE_DIRECTORY.'/'.$file->getFilename(), 'compact' => self::compactString(pathinfo($file->getFilename(), PATHINFO_FILENAME)), 'priority' => self::extensionPriority($file->getExtension()), 'length' => strlen($file->getFilename()), ]; }) ->sortBy([ ['priority', 'asc'], ['length', 'asc'], ['path', 'asc'], ]) ->values() ->all(); } private static function scoreImageMatch(array $image, array $fullCompactVariants, array $modelCompactVariants, array $brandVariants): int { $score = 0; $fileCompact = $image['compact']; if ($brandVariants !== [] && ! collect($brandVariants)->contains(fn ($brand) => Str::contains($fileCompact, $brand))) { return 0; } foreach ($fullCompactVariants as $variant) { if ($variant !== '' && $fileCompact === $variant) { return 160; } if ($variant !== '' && Str::contains($fileCompact, $variant)) { $score = max($score, 125); } } foreach ($modelCompactVariants as $variant) { if ($variant !== '' && $fileCompact === $variant) { return 150; } if ($variant !== '' && Str::contains($fileCompact, $variant)) { $score = max($score, 115); } } $meaningfulTokens = self::meaningfulTokens($modelCompactVariants[0] ?? ''); $matchedTokens = array_values(array_filter( $meaningfulTokens, fn ($token) => Str::contains($fileCompact, $token) )); if ($meaningfulTokens !== [] && count($matchedTokens) === count($meaningfulTokens)) { $score = max($score, 95); } elseif (count($matchedTokens) >= 3) { $score = max($score, 75); } return $score; } private static function compactVariants(string $value): array { $normalized = Str::lower(Str::ascii($value)); $baseVariants = [ $normalized, preg_replace('/\bf\s*\/?\s*\d+(?:\.\d+)?\b/u', '', $normalized) ?? $normalized, ]; $variants = []; $queue = array_map(self::compactString(...), $baseVariants); while ($queue !== []) { $variant = array_shift($queue); if ($variant === '' || in_array($variant, $variants, true)) { continue; } $variants[] = $variant; foreach (['canon', 'sony', 'fujifilm', 'fujinon', 'nikon', 'nikkor', 'lensa'] as $brand) { if (Str::startsWith($variant, $brand)) { $queue[] = Str::after($variant, $brand); } } if (Str::contains($variant, 'mount')) { $queue[] = str_replace('mount', '', $variant); } } return $variants; } private static function compactString(string $value): string { return (string) preg_replace('/[^a-z0-9]+/', '', Str::lower(Str::ascii($value))); } private static function meaningfulTokens(string $compact): array { preg_match_all('/[a-z]+|\d+/', $compact, $matches); return array_values(array_filter( $matches[0] ?? [], fn ($token) => strlen($token) >= 2 || ctype_digit($token) )); } private static function extensionPriority(string $extension): int { return match (strtolower($extension)) { 'png' => 1, 'jpg', 'jpeg' => 2, 'webp' => 3, default => 9, }; } private static function brandVariants(string $brand): array { $brandCompact = self::compactString($brand); return match ($brandCompact) { 'canon' => ['canon'], 'sony' => ['sony'], 'fujifilm' => ['fujifilm', 'fujinon'], 'nikon' => ['nikon', 'nikkor'], default => array_values(array_filter([$brandCompact])), }; } }