// Package store defines the persistence contract for the URL shortener.
//
// The headline of this package is the small [Store] interface. The service and
// HTTP handlers depend only on that interface, never on a concrete backend, so
// swapping implementations (in-memory map vs. JSON file) is a one-line change in
// main.go. This is dependency inversion in practice.
package store

import (
	"context"
	"errors"
	"time"
)

// Sentinel errors returned by every [Store] implementation. Callers compare
// with errors.Is so that wrapped errors still match.
var (
	// ErrNotFound is returned when no link exists for a given code.
	ErrNotFound = errors.New("link not found")
	// ErrCodeTaken is returned by Save when the code already exists.
	ErrCodeTaken = errors.New("code already taken")
)

// Link is the domain entity: a mapping from a short code to a long URL plus
// bookkeeping metadata.
type Link struct {
	Code      string     `json:"code"`                 // short code, e.g. "aB3xK"
	LongURL   string     `json:"long_url"`             // original destination
	CreatedAt time.Time  `json:"created_at"`           // creation timestamp (UTC)
	Hits      int64      `json:"hits"`                 // redirect counter
	ExpiresAt *time.Time `json:"expires_at,omitempty"` // optional TTL; nil == never
}

// Clone returns a deep copy of the link so callers cannot mutate a store's
// internal state through a returned pointer.
func (l *Link) Clone() *Link {
	if l == nil {
		return nil
	}
	cp := *l
	if l.ExpiresAt != nil {
		t := *l.ExpiresAt
		cp.ExpiresAt = &t
	}
	return &cp
}

// Store is the persistence boundary. It is deliberately tiny: a handful of
// methods is far easier to implement a second time than a sprawling one, and a
// small surface keeps the conformance suite (store_test.go) tractable.
//
// Implementations must be safe for concurrent use by multiple goroutines.
type Store interface {
	// Save persists a new link. It returns [ErrCodeTaken] if l.Code already
	// exists. The store takes ownership of a copy of l.
	Save(ctx context.Context, l *Link) error
	// Get returns a copy of the link for code, or [ErrNotFound].
	Get(ctx context.Context, code string) (*Link, error)
	// IncrementHits atomically bumps the hit counter, or returns [ErrNotFound].
	IncrementHits(ctx context.Context, code string) error
	// Delete removes a link, returning [ErrNotFound] if absent.
	Delete(ctx context.Context, code string) error
	// NextID returns a monotonically increasing id, used by the sequential
	// base62 code strategy.
	NextID(ctx context.Context) (uint64, error)
	// Close releases any resources held by the store (files, handles).
	Close() error
}
