<?php
// helpers/barcode_utils.php
// Utilities for generating, normalizing, and classifying product barcodes.

/**
 * Keep only digits from a scanned string (USB scanners sometimes include extra chars).
 */
function digits_only(string $s): string {
    return preg_replace('/\D+/', '', $s) ?? '';
}

/**
 * Normalize a barcode value coming from forms or scanners.
 * For inventory barcodes we store digits only.
 */
function normalize_barcode_value(?string $value): string
{
    return digits_only(trim((string)($value ?? '')));
}

/**
 * Validate allowed manual barcode input.
 *
 * Rules:
 * - must contain digits only after normalization
 * - minimum length is 12 digits
 * - maximum length is 32 digits (matches current database schema)
 */
function is_supported_product_barcode(string $barcode): bool
{
    $barcode = normalize_barcode_value($barcode);
    $len = strlen($barcode);
    return $barcode !== '' && $len >= 12 && $len <= 32;
}

/**
 * Detect a barcode type label for storage/reporting.
 *
 * This is descriptive only. Lookup works by barcode_value itself.
 */
function detect_barcode_type(string $barcode, bool $generated = false): string
{
    $barcode = normalize_barcode_value($barcode);
    if ($barcode === '') {
        return 'UNKNOWN';
    }

    if ($generated) {
        return 'UPC_A';
    }

    if (strlen($barcode) === 12 && is_valid_upc_a($barcode)) {
        return 'UPC_A';
    }

    if (strlen($barcode) === 13) {
        return 'EAN_13';
    }

    if (strlen($barcode) === 14) {
        return 'GTIN_14';
    }

    return 'CUSTOM';
}

/**
 * Calculate UPC-A check digit from an 11-digit numeric string.
 * UPC-A algorithm:
 *  - Sum digits in odd positions (1,3,5,...,11) and multiply by 3
 *  - Add sum of digits in even positions (2,4,...,10)
 *  - Check digit = (10 - (total % 10)) % 10
 */
function upc_a_check_digit(string $elevenDigits): int {
    $elevenDigits = digits_only($elevenDigits);
    if (strlen($elevenDigits) !== 11) {
        throw new InvalidArgumentException('UPC-A check digit requires 11 digits');
    }

    $oddSum = 0;
    $evenSum = 0;
    for ($i = 0; $i < 11; $i++) {
        $d = (int)$elevenDigits[$i];
        $pos = $i + 1; // 1-indexed
        if (($pos % 2) === 1) $oddSum += $d;
        else $evenSum += $d;
    }
    $total = ($oddSum * 3) + $evenSum;
    return (10 - ($total % 10)) % 10;
}

/**
 * Generate a UPC-A (12 digits) based on a fixed prefix + the product id.
 *
 * NOTE: This is an INTERNAL barcode generator. If you need GS1-compliant UPCs,
 * you must use an assigned company prefix.
 */
function generate_upc_a_from_product_id(int $productId, string $prefix = '200'): string {
    if ($productId <= 0) throw new InvalidArgumentException('Invalid product id');
    $prefix = digits_only($prefix);
    if ($prefix === '') $prefix = '200';

    // Build 11 digits: prefix + (11-len(prefix)) digits of product id
    $bodyLen = 11 - strlen($prefix);
    if ($bodyLen <= 0) {
        throw new InvalidArgumentException('Prefix too long for UPC-A');
    }

    // If product id is too large, we still keep the last N digits.
    $idDigits = digits_only((string)$productId);
    if (strlen($idDigits) > $bodyLen) {
        $idDigits = substr($idDigits, -$bodyLen);
    }
    $base11 = $prefix . str_pad($idDigits, $bodyLen, '0', STR_PAD_LEFT);

    $check = upc_a_check_digit($base11);
    return $base11 . (string)$check;
}

/**
 * Validate a UPC-A barcode (12 digits).
 */
function is_valid_upc_a(string $twelveDigits): bool {
    $twelveDigits = digits_only($twelveDigits);
    if (strlen($twelveDigits) !== 12) return false;
    $base11 = substr($twelveDigits, 0, 11);
    $check = (int)substr($twelveDigits, 11, 1);
    try {
        return upc_a_check_digit($base11) === $check;
    } catch (Throwable $e) {
        return false;
    }
}


/**
 * Build normalized candidate values for scanned/typed codes.
 *
 * This helps when a scanner returns extra characters, spaces, or UPC/EAN
 * compatibility variants with a leading zero.
 *
 * @return string[]
 */
function barcode_lookup_candidates(?string $value): array
{
    $raw = trim((string)($value ?? ''));
    if ($raw === '') {
        return [];
    }

    $digits = digits_only($raw);
    $candidates = [];
    $push = static function (string $candidate) use (&$candidates): void {
        $candidate = trim($candidate);
        if ($candidate !== '' && !in_array($candidate, $candidates, true)) {
            $candidates[] = $candidate;
        }
    };

    $push($raw);
    $push($digits);

    if ($digits !== '') {
        $push(ltrim($digits, '0'));

        // UPC-A sometimes comes back from scanners as EAN-13 with a leading zero.
        if (strlen($digits) === 13 && $digits[0] === '0') {
            $push(substr($digits, 1));
        }

        // And some datasets may store a UPC-A with a compatibility leading zero.
        if (strlen($digits) === 12) {
            $push('0' . $digits);
        }
    }

    return array_values(array_filter($candidates, static fn($v) => $v !== ''));
}

