// Package ident — единая точка генерации и разбора идентификаторов (ULID).
//
// ULID сортируется по времени создания, компактен в логах и URL (без дефисов)
// и глобально уникален across сущностей: поиск по голому id находит все
// упоминания. Канонический вид — нижний регистр.
package ident

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/oklog/ulid/v2"
)

// ErrInvalid — синтаксически некорректный идентификатор. Такой id трактуем
// как несуществующую сущность, без похода в БД.
var ErrInvalid = errors.New("некорректный идентификатор")

// NewID возвращает новый ULID в нижнем регистре.
func NewID() string {
	return strings.ToLower(ulid.Make().String())
}

// TimeOf возвращает время создания идентификатора: UTC, секундная точность —
// ровно та форма, в которой время хранится (см. store.Now).
//
// Нужен пересборке витрины: у тела, лежащего в архиве без учётной записи,
// другого источника метки приёма нет. Дата каталога архива не годится — она
// задаёт сутки, а порядок проигрывания нужен внутри суток.
//
// `.UTC()` здесь обязателен и не для красоты: ulid.Time собирает время через
// time.Unix, то есть в локальной зоне машины, а Truncate работает с абсолютной
// длительностью и зону не нормализует. Без приведения метка подобранного тела
// сравнивалась бы с меткой из БД по-разному на разных машинах.
func TimeOf(s string) (time.Time, error) {
	id, err := ulid.ParseStrict(strings.ToUpper(strings.TrimSpace(s)))
	if err != nil {
		return time.Time{}, fmt.Errorf("%w: %q", ErrInvalid, s)
	}
	return ulid.Time(id.Time()).UTC().Truncate(time.Second), nil
}

// Parse валидирует внешний идентификатор и приводит его к каноническому виду.
// Вызывается на входных границах (HTTP, CLI) до запроса к БД: сравнение строк
// в SQLite побайтовое, а base32 ULID при декодировании нечувствителен к
// регистру.
func Parse(s string) (string, error) {
	id, err := ulid.ParseStrict(strings.ToUpper(strings.TrimSpace(s)))
	if err != nil {
		return "", fmt.Errorf("%w: %q", ErrInvalid, s)
	}
	return strings.ToLower(id.String()), nil
}
