<?php

declare(strict_types=1);

namespace App\Support\Filament\Slugging;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;

trait GeneratesDailySequenceSlug
{
    /**
     * @param class-string<\Illuminate\Database\Eloquent\Model> $modelClass
     */
    protected function createRecordWithDailySequenceSlug(
        string $modelClass,
        array $data,
        string $slugColumn = 'slug',
        int $maxRetries = 10,
    ): Model {
        $lastException = null;

        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            $data[$slugColumn] = $this->generateDailySequenceSlug($modelClass, $slugColumn);

            try {
                /** @var Model $record */
                $record = $modelClass::create($data);

                return $record;
            } catch (QueryException $e) {
                $lastException = $e;

                if (! $this->isDuplicateKeyException($e)) {
                    throw $e;
                }
            }
        }

        throw $lastException ?? new \RuntimeException('Failed to create record with generated slug.');
    }

    /**
     * @param class-string<\Illuminate\Database\Eloquent\Model> $modelClass
     */
    protected function generateDailySequenceSlug(string $modelClass, string $slugColumn = 'slug'): string
    {
        $prefix = now()->format('Ymd');

        if (! preg_match('/^[A-Za-z0-9_]+$/', $slugColumn)) {
            throw new \InvalidArgumentException('Invalid slug column.');
        }

        $maxSequence = $modelClass::withTrashed()
            ->where($slugColumn, 'like', $prefix . '-%')
            ->max(DB::raw("CAST(SUBSTRING_INDEX({$slugColumn}, '-', -1) AS UNSIGNED)"));

        $nextSequence = ((int) $maxSequence) + 1;

        return $prefix . '-' . $nextSequence;
    }

    protected function isDuplicateKeyException(QueryException $e): bool
    {
        $sqlState = $e->errorInfo[0] ?? null;
        $driverCode = $e->errorInfo[1] ?? null;
        $message = $e->getMessage();

        if ($sqlState === '23000' && (int) $driverCode === 1062) {
            return true;
        }

        return str_contains($message, 'Duplicate entry');
    }
}
