<?php

namespace App\Support;

use App\Models\Question;

class AnswerEvaluator
{
    public static function isAutomaticallyGradable(?string $type): bool
    {
        return in_array($type, ['pg', 'pg_kompleks', 'menjodohkan', 'isian'], true);
    }

    public static function evaluate(Question $question, mixed $studentAnswer): string
    {
        return self::isCorrect($question, $studentAnswer) ? 'Y' : 'N';
    }

    public static function isCorrect(Question $question, mixed $studentAnswer): bool
    {
        $type = $question->type ?? 'pg';
        $correctAnswer = self::normalizeValue($question->answer);
        $studentAnswer = self::normalizeValue($studentAnswer);

        return match ($type) {
            'pg' => self::normalizeScalar($correctAnswer) !== null
                && self::normalizeScalar($correctAnswer) === self::normalizeScalar($studentAnswer),
            'pg_kompleks' => self::normalizeOptionSet($correctAnswer) === self::normalizeOptionSet($studentAnswer),
            'menjodohkan' => self::normalizeMatchingPairs($correctAnswer) === self::normalizeMatchingPairs($studentAnswer),
            'isian' => self::normalizeText($correctAnswer) !== ''
                && self::normalizeText($correctAnswer) === self::normalizeText($studentAnswer),
            default => false,
        };
    }

    public static function normalizeValue(mixed $value): mixed
    {
        $normalized = $value;

        for ($i = 0; $i < 3; $i++) {
            if (!is_string($normalized)) {
                break;
            }

            $trimmed = trim($normalized);
            if ($trimmed === '') {
                return '';
            }

            $decoded = json_decode($trimmed, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                break;
            }

            $normalized = $decoded;
        }

        return $normalized;
    }

    private static function normalizeScalar(mixed $value): ?string
    {
        $value = self::normalizeValue($value);

        if (is_array($value)) {
            $value = reset($value);
        }

        if ($value === null) {
            return null;
        }

        $stringValue = trim((string) $value);

        return $stringValue === '' ? null : $stringValue;
    }

    private static function normalizeOptionSet(mixed $value): array
    {
        $value = self::normalizeValue($value);

        if (!is_array($value)) {
            $scalar = self::normalizeScalar($value);

            return $scalar === null ? [] : [$scalar];
        }

        $normalized = array_values(array_filter(array_map(function ($item) {
            return self::normalizeScalar($item);
        }, $value), fn ($item) => $item !== null));

        sort($normalized);

        return $normalized;
    }

    private static function normalizeMatchingPairs(mixed $value): array
    {
        $value = self::normalizeValue($value);

        if (!is_array($value)) {
            return [];
        }

        $pairs = [];

        foreach ($value as $pair) {
            if (!is_array($pair)) {
                continue;
            }

            $premis = trim((string) ($pair['premis'] ?? ''));
            $respon = trim((string) ($pair['respon'] ?? ''));

            if ($premis === '' && $respon === '') {
                continue;
            }

            $pairs[] = [
                'premis' => $premis,
                'respon' => $respon,
            ];
        }

        usort($pairs, function ($left, $right) {
            return [$left['premis'], $left['respon']] <=> [$right['premis'], $right['respon']];
        });

        return $pairs;
    }

    private static function normalizeText(mixed $value): string
    {
        $value = self::normalizeValue($value);

        if (is_array($value)) {
            $value = implode(' ', array_map(function ($item) {
                return is_scalar($item) ? (string) $item : '';
            }, $value));
        }

        return mb_strtolower(trim((string) $value));
    }
}
