<?php
namespace Core\Services;

class LabParserService {

    /**
     * Parses raw OCR text into structured lab results.
     * Returns: [['test_name','result_value','unit','normal_range','is_abnormal','needs_review'] ...]
     */
    public function parse($rawText) {
        $lines = explode("\n", $rawText);
        $results = [];

        $ignoreStarts = [
            'test', 'result', 'range', 'unit', 'reference', 'patient', 'doctor',
            'date', 'sex', 'male', 'female', 'signature', 'approved', 'laboratory',
            'name', 'age', 'id', 'report', 'page', 'printed',
            'مختبر', 'اسم', 'الرقم', 'العمر', 'الجنس',
        ];

        foreach ($lines as $line) {
            $line = trim($line);
            if (empty($line) || mb_strlen($line) < 5) continue;

            // Skip header-like lines
            $lower = mb_strtolower($line, 'UTF-8');
            $skip = false;
            foreach ($ignoreStarts as $word) {
                if (mb_strpos($lower, $word) === 0) { $skip = true; break; }
            }
            if ($skip) continue;

            $result = $this->matchLine($line);
            if ($result) {
                $results[] = $result;
            }
        }

        return $results;
    }

    /**
     * Try multiple patterns (most specific → least) against a single line.
     * Name: any word chars including digits (HbA1c, 25-OH), Arabic, spaces, parens
     * Value: optional </> prefix, digits, optional decimal, optional % suffix
     * Unit: alpha/slash/% token
     * Range: N-N  or  <N  or  >N  (trailing unit stripped)
     * 
     * NOTE: No strict $ anchor so trailing content (flags, status words) is allowed.
     */
    private function matchLine($line) {
        // Character class for test names: letters+digits+Arabic+spaces+hyphens+parens
        $name  = '(?P<name>[A-Za-z\x{0600}-\x{06FF}][A-Za-z0-9\x{0600}-\x{06FF}\s\-\(\)]*?)';
        $sep   = '[\s\t]+';
        // Value: optional < or >, optional space, digits, optional decimal, optional %
        $val   = '(?P<val>[<>]?\s*\d+(?:[.,]\d+)?%?)';
        // Unit: alpha/slash/percent — not starting with a digit
        $unit  = '(?P<unit>[A-Za-z%\xb5][A-Za-z0-9%\/\xb5\-]*)';
        // Range: either N-N or <N or >N, with optional trailing unit suffix (stripped later)
        $rng   = '(?P<range>(?:[<>]\s*\d+(?:[.,]\d+)?|\d+(?:[.,]\d+)?\s*[-\x{2013}]\s*\d+(?:[.,]\d+)?)[A-Za-z%\/\xb5]*)';
        $ws    = '\s+';

        $patterns = [
            // 1: Name Value Unit Range
            "/^{$name}{$sep}{$val}{$ws}{$unit}{$ws}{$rng}/u",
            // 2: Name Value Range (no unit)
            "/^{$name}{$sep}{$val}{$ws}{$rng}/u",
            // 3: Name Value Unit (range missing)
            "/^{$name}{$sep}{$val}{$ws}{$unit}/u",
            // 4: Name Value only
            "/^{$name}{$sep}{$val}/u",
        ];

        foreach ($patterns as $idx => $pattern) {
            if (preg_match($pattern, $line, $m)) {
                // Sanity: name must not be purely numeric
                $nameTrim = trim($m['name']);
                if (empty($nameTrim) || is_numeric($nameTrim)) continue;
                // Sanity: value must contain at least one digit
                if (!preg_match('/\d/', $m['val'])) continue;

                $unit  = trim($m['unit']  ?? '');
                $range = trim($m['range'] ?? '');

                // If pattern 2 matched and "range" looks like a unit (e.g. "mg"), reclassify
                if ($idx === 1 && preg_match('/^[A-Za-z%\/]+$/', $range) && !preg_match('/\d/', $range)) {
                    $unit  = $range;
                    $range = '';
                }

                return $this->formatResult($nameTrim, trim($m['val']), $unit, $range, $idx >= 2);
            }
        }

        return null;
    }

    private function formatResult($name, $valueStr, $unit, $rangeRaw, $forceNeedsReview = false) {
        if (empty($name) || empty($valueStr)) return null;

        // Normalise value: remove spaces, standardise decimal separator
        $valueStr = preg_replace('/\s+/', '', $valueStr);
        $valueStr = str_replace(',', '.', $valueStr);

        // Normalise range
        $rangeRaw = trim($rangeRaw);
        $rangeRaw = str_ireplace(['to', "\xe2\x80\x93", ' – '], ['-', '-', '-'], $rangeRaw); // 'to', en-dash
        // Strip trailing unit letters e.g. "70-100mg/dL" → "70-100", "<5.0mmol" → "<5.0"
        $rangeRaw = preg_replace('/^(<)(\d+(?:[.,]\d+)?)[A-Za-z\/%\xb5]+$/u', '$1$2', $rangeRaw);
        $rangeRaw = preg_replace('/^(>)(\d+(?:[.,]\d+)?)[A-Za-z\/%\xb5]+$/u', '$1$2', $rangeRaw);
        $rangeRaw = preg_replace('/^(\d+(?:[.,]\d+)?)\s*-\s*(\d+(?:[.,]\d+)?)[A-Za-z\/%\xb5]*$/u', '$1-$2', $rangeRaw);
        $rangeRaw = preg_replace('/\s+/', '', $rangeRaw); // remove internal spaces
        $rangeRaw = str_replace(',', '.', $rangeRaw);

        $needsReview = $forceNeedsReview ? 1 : 0;
        $isAbnormal  = false;

        // Numeric value (strip < > % for comparison)
        $floatVal = (float) preg_replace('/[^0-9.]/', '', $valueStr);

        if (!empty($rangeRaw)) {
            $isAbnormal = $this->checkAbnormal($floatVal, $rangeRaw);
        } else {
            $needsReview = 1;
        }

        if (!preg_match('/\d/', $valueStr)) $needsReview = 1;

        return [
            'test_name'    => $name,
            'result_value' => $valueStr,
            'unit'         => $unit,
            'normal_range' => $rangeRaw,
            'is_abnormal'  => $isAbnormal ? 1 : 0,
            'needs_review' => $needsReview,
        ];
    }

    private function checkAbnormal($value, $rangeStr) {
        // N-N format
        if (preg_match('/^(\d+\.?\d*)-(\d+\.?\d*)$/', $rangeStr, $p)) {
            return $value < (float)$p[1] || $value > (float)$p[2];
        }
        // <N  (value should be LESS than N)
        if (preg_match('/^<(\d+\.?\d*)$/', $rangeStr, $p)) {
            return $value >= (float)$p[1];
        }
        // >N  (value should be MORE than N)
        if (preg_match('/^>(\d+\.?\d*)$/', $rangeStr, $p)) {
            return $value <= (float)$p[1];
        }
        return false;
    }
}
