<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

trait HasCodeGeneration
{
    /**
     * Ensure a code exists on the given attribute. Generates if empty.
     *
     * @param  string  $attribute   e.g., 'sku_code' or 'barcode'
     * @param  string  $type        built-in: 'sku_code' | 'barcode'
     * @param  array   $options     [
     *   'scope' => ['product_inventory_id'], // columns for scoped uniqueness
     *   'prefix' => 'SKU-',                  // for sku_code
     *   'length' => 8,                       // for sku_code random length
     *   'formatter' => fn(Model $m) => '...',// custom generator (overrides type)
     *   'barcode_standard' => 'ean13',       // 'ean13' | 'random'
     *   'max_attempts' => 50,
     * ]
     */
    public function ensureCode(string $attribute, string $type = 'sku_code', array $options = []): void
    {
        if (!blank($this->{$attribute})) {
            return;
        }

        $maxAttempts = (int) ($options['max_attempts'] ?? 50);

        for ($i = 0; $i < $maxAttempts; $i++) {
            $candidate = $this->makeCodeCandidate($type, $options);

            if ($this->isCodeUnique($attribute, $candidate, Arr::get($options, 'scope', []))) {
                $this->{$attribute} = $candidate;
                return;
            }
        }

        throw new \RuntimeException("Failed to generate unique {$attribute} after {$maxAttempts} attempts.");
    }

    /** Build a candidate code based on type or a custom formatter. */
    protected function makeCodeCandidate(string $type, array $options): string
    {
        // Custom formatter takes full control
        if (isset($options['formatter']) && is_callable($options['formatter'])) {
            return (string) call_user_func($options['formatter'], $this);
        }

        return match ($type) {
            'sku_code' => $this->makeSkuCode(
                prefix: (string) ($options['prefix'] ?? 'SKU-'),
                length: (int) ($options['length'] ?? 8)
            ),
            'barcode'  => $this->makeBarcode(
                standard: (string) ($options['barcode_standard'] ?? 'ean13')
            ),
            default    => Str::upper(Str::random((int) ($options['length'] ?? 12))),
        };
    }

    /** Default SKU format: PREFIX + uppercase A‑Z0‑9 (no confusing chars). */
    protected function makeSkuCode(string $prefix = 'SKU-', int $length = 8): string
    {
        // Base36 gives 0-9A-Z, then uppercase it
        $rand = Str::upper(Str::random($length));
        // Optional: tighten to [A-Z0-9] only
        $rand = preg_replace('/[^A-Z0-9]/', 'X', $rand);
        return $prefix . $rand;
    }

    /**
     * Generate a barcode string.
     * - 'ean13' → 13-digit numeric with checksum
     * - 'random' → 16-digit numeric
     */
    protected function makeBarcode(string $standard = 'ean13'): string
    {
        if ($standard === 'ean13') {
            // 12 random digits + checksum
            $digits = '';
            for ($i = 0; $i < 12; $i++) {
                $digits .= random_int(0, 9);
            }
            return $digits . $this->ean13Checksum($digits);
        }

        // Fallback: 16-digit numeric
        $digits = '';
        for ($i = 0; $i < 16; $i++) {
            $digits .= random_int(0, 9);
        }
        return $digits;
    }

    /** Calculate EAN‑13 checksum for a 12-digit string. */
    protected function ean13Checksum(string $twelveDigits): int
    {
        $sum = 0;
        for ($i = 0; $i < 12; $i++) {
            $digit = (int) $twelveDigits[$i];
            $sum += ($i % 2 === 0) ? $digit : $digit * 3;
        }
        return (10 - ($sum % 10)) % 10;
    }

    /**
     * Check uniqueness for $attribute == $candidate, optionally scoped.
     * Excludes current model (on update).
     */
    protected function isCodeUnique(string $attribute, string $candidate, array $scope = []): bool
    {
        /** @var \Illuminate\Database\Eloquent\Builder $query */
        $query = static::query()->where($attribute, $candidate);

        foreach ($scope as $col) {
            // Use current value on the model for scoping
            $query->where($col, $this->getAttribute($col));
        }

        // Exclude self if model exists
        if ($this->exists && $this->getKey()) {
            $query->whereKeyNot($this->getKey());
        }

        return !$query->exists();
    }
}
