<?php

declare(strict_types=1);

namespace App\Business\SystemRule;

use App\Models\DberpNumberSequence;
use Illuminate\Support\Facades\DB;

/**
 * 编号生成与唯一性校验入口（后续业务单可直接调用）。
 */
final class NumberSequenceAllocator
{
    public function findEnabledRule(string $appliesTo): ?DberpNumberSequence
    {
        return DberpNumberSequence::query()
            ->where('applies_to', $appliesTo)
            ->where('is_enabled', true)
            ->orderBy('sort_order')
            ->orderBy('id')
            ->first();
    }

    /**
     * 根据当前规则生成预览（不落库、不推进序号）。
     */
    public function preview(DberpNumberSequence $rule): string
    {
        $datePart = $this->formatDateSegment((string) $rule->date_segment);
        $serial = str_pad((string) $rule->next_value, max(1, (int) $rule->serial_digits), '0', STR_PAD_LEFT);

        return $rule->prefix.$datePart.$serial;
    }

    /**
     * 预留：在事务内锁定行并推进序号，供后续单据保存时调用。
     *
     * @throws \RuntimeException 规则未启用时
     */
    public function reserveNext(DberpNumberSequence $rule): string
    {
        if (! $rule->is_enabled) {
            throw new \RuntimeException(__('dberp.errors.number_rule_disabled'));
        }

        return DB::transaction(function () use ($rule): string {
            /** @var DberpNumberSequence $locked */
            $locked = DberpNumberSequence::query()->whereKey($rule->getKey())->lockForUpdate()->firstOrFail();
            $preview = $this->preview($locked);
            $locked->next_value = ((int) $locked->next_value) + 1;
            $locked->save();

            return $preview;
        });
    }

    public function reserveByAppliesTo(string $appliesTo): ?string
    {
        $rule = $this->findEnabledRule($appliesTo);

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

        return $this->reserveNext($rule);
    }

    private function formatDateSegment(string $segment): string
    {
        $now = now();

        return match ($segment) {
            'yyyymm' => $now->format('Ym'),
            'yyyymmdd' => $now->format('Ymd'),
            default => '',
        };
    }
}
