<?php

namespace Modules\NX600Lab\Entities\Drivers;

use Modules\LabCore\Entities\Contracts\LabDeviceDriver;
use Modules\NX600Lab\Entities\NxModuleSetting;
use Modules\NX600Lab\Entities\Services\Nx600Protocol;

/**
 * NX600 analyzer driver for the LabCore engine.
 *
 * This makes NX600Lab a first-class vendor of LabCore: the module keeps its
 * own `nx_*` tables, protocol parsers and console servers, but it also
 * advertises itself through LabCore's DriverRegistry so the shared LabCore
 * UI, agent-sync API and device registry can recognise NX600 devices.
 *
 * NX600 speaks its own (non-ASTM) protocol, so the heavy parsing stays in
 * {@see \Modules\NX600Lab\Entities\Services\Nx600Protocol}; this driver only
 * advertises capabilities and normalizes an already-parsed payload into the
 * canonical LabCore shape.
 */
class Nx600Driver implements LabDeviceDriver
{
    public function vendor(): string
    {
        return 'nx600';
    }

    public function label(): string
    {
        return 'NX600 Analyzer';
    }

    public function models(): array
    {
        return ['NX600'];
    }

    public function commProtocols(): array
    {
        return [
            'nx600-type-1', 'nx600-type-2', 'nx600-type-3',
            'nx600-type-A', 'nx600-type-B', 'nx600-hl7',
        ];
    }

    public function connectionTypes(): array
    {
        return ['tcp', 'serial', 'serial2eth'];
    }

    /**
     * Representative NX600 chemistry catalog. Reference ranges are indicative
     * and meant to be tuned per-business in lab_analyte_map.
     */
    public function defaultAnalyteMap(): array
    {
        $chem = [
            ['GLU',  'Glucose',          'mg/dL', '70',  '143'],
            ['BUN',  'Urea Nitrogen',    'mg/dL', '7',   '27'],
            ['CRE',  'Creatinine',       'mg/dL', '0.3', '1.2'],
            ['TP',   'Total Protein',    'g/dL',  '5.2', '8.2'],
            ['ALB',  'Albumin',          'g/dL',  '2.2', '3.9'],
            ['GOT',  'AST (GOT)',        'U/L',   '0',   '50'],
            ['GPT',  'ALT (GPT)',        'U/L',   '0',   '50'],
            ['ALP',  'Alk. Phosphatase', 'U/L',   '23',  '212'],
            ['TCHO', 'Total Cholesterol','mg/dL', '110', '320'],
            ['TG',   'Triglycerides',    'mg/dL', '29',  '291'],
            ['TBIL', 'Total Bilirubin',  'mg/dL', '0',   '0.9'],
            ['Ca',   'Calcium',          'mg/dL', '7.9', '12'],
        ];

        $rows = [];
        foreach ($chem as $i => [$code, $name, $unit, $lo, $hi]) {
            $rows[] = [
                'analyte_code'   => $code,
                'canonical_name' => $name,
                'display_name'   => $name,
                'unit'           => $unit,
                'species'        => null,
                'ref_lower'      => $lo,
                'ref_upper'      => $hi,
                'category'       => 'chemistry',
                'sort_order'     => $i,
            ];
        }
        return $rows;
    }

    /**
     * Parse a raw NX600 frame. Only Command R (test result) frames yield a
     * payload; any other command (worklist/sample-info/error) returns null.
     *
     * @param  array $context  May carry 'comm_protocol' (e.g. nx600-type-3)
     *                         and 'business_id'.
     */
    public function parse(string $raw, array $context = []): ?array
    {
        $protocol = new Nx600Protocol();
        $commType = $this->commTypeFromContext($context);

        [$frames] = $protocol->extractFrames($raw);
        if (empty($frames)) {
            return null;
        }

        $frame = $commType === '3'
            ? $protocol->parseType3Frame($frames[0])
            : $protocol->parseFrame($frames[0], true);

        if (!$frame || isset($frame['bcc_error']) || ($frame['command'] ?? null) !== Nx600Protocol::CMD_TEST_RESULT) {
            return null;
        }

        $parsed = $commType === '3'
            ? $protocol->parseTestResultType3($frame)
            : $protocol->parseTestResult($frame, $commType);

        $parsed['comm_type']   = $commType;
        $parsed['business_id'] = $context['business_id'] ?? null;

        return $parsed;
    }

    private function commTypeFromContext(array $context): string
    {
        $protocol = (string) ($context['comm_protocol'] ?? 'nx600-type-1');
        if (preg_match('/nx600-type-([123AB])/i', $protocol, $m)) {
            return strtoupper($m[1]);
        }
        return '1';
    }

    /**
     * Map an Nx600Protocol-parsed result payload onto the canonical LabCore
     * lab_test_sessions / lab_test_results shape.
     *
     * @param  array $parsed  Output of Nx600Protocol::parseTestResult().
     * @return array{session: array, results: array}
     */
    public function normalize(array $parsed): array
    {
        $businessId  = $parsed['business_id'] ?? null;
        $speciesMap  = NxModuleSetting::speciesMap($businessId);
        $speciesCode = $parsed['species'] ?? null;

        $session = [
            'vendor'         => 'nx600',
            'sample_no'      => $parsed['sample_no'] ?? '',
            'patient_id'     => $parsed['patient_id'] ?: null,
            'patient_name'   => $parsed['patient_name'] ?: null,
            'test_condition' => $parsed['test_condition'] ?? 'NORMAL',
            'test_date'      => $parsed['test_date'] ?? null,
            'test_time'      => $parsed['test_time'] ?? null,
            'sample_type'    => $parsed['sample_type'] ?? null,
            'species'        => $speciesMap[$speciesCode] ?? null,
            'species_code'   => $speciesCode !== null ? (string) $speciesCode : null,
            'sex'            => $this->sexLabel($parsed['sex'] ?? 9),
            'age'            => isset($parsed['age']) && $parsed['age'] < 999 ? (int) $parsed['age'] : null,
            'operator_id'    => $parsed['operator_id'] ?? null,
            'analyzer_id'    => $parsed['analyzer_id'] ?? null,
            'source'         => 'agent',
            'meta'           => [
                'sample_position' => $parsed['sample_position'] ?? null,
                'emergency_test'  => $parsed['emergency_test'] ?? false,
                'slide_lot'       => $parsed['slide_lot'] ?? null,
                'valid_term'      => $parsed['valid_term'] ?? null,
                'comm_type'       => $parsed['comm_type'] ?? null,
            ],
        ];

        $results = [];
        foreach ($parsed['tests'] ?? [] as $t) {
            $results[] = [
                'analyte_code'    => $t['test_name'] ?? '',
                'analyte_name'    => $t['base_name'] ?? null,
                'base_name'       => $t['base_name'] ?? null,
                'sample_suffix'   => $t['sample_suffix'] ?? null,
                'equality'        => $t['equality'] ?? '=',
                'value'           => $t['result'] ?? null,
                'unit'            => $t['unit'] ?? null,
                'dilution_factor' => $t['dilution_factor'] ?? 1,
                'ref_lower'       => $t['ref_lower'] ?? null,
                'ref_upper'       => $t['ref_upper'] ?? null,
                'raw_flag'        => $t['raw_warning'] ?? null,
                'flags'           => array_filter([
                    'high'             => $t['warn_high'] ?? false,
                    'low'              => $t['warn_low'] ?? false,
                    'out_of_range'     => $t['warn_out_of_range'] ?? false,
                    'slide_expired'    => $t['warn_slide_expired'] ?? false,
                    'temp_error'       => $t['warn_temp_error'] ?? false,
                    'photometer_error' => $t['warn_photometer_error'] ?? false,
                    'high_value'       => $t['warn_high_value'] ?? false,
                    'calc_error'       => $t['warn_calc_error'] ?? false,
                    'unspotted'        => $t['warn_unspotted'] ?? false,
                ]),
                'is_abnormal'     => $t['is_abnormal'] ?? false,
            ];
        }

        return ['session' => $session, 'results' => $results];
    }

    private function sexLabel(int $code): string
    {
        return match ($code) {
            0       => 'Male',
            1       => 'Female',
            default => 'Unknown',
        };
    }
}
