// Package domain holds Tapir's core entities. It depends on nothing outside
// the standard library — no providers, no storage, no AI. See docs/data-model.md.
package domain

import (
	"errors"
	"fmt"
	"time"
)

// ErrVideoNotFound is returned when a video id resolves to no video (deleted,
// private, or a typo'd paste). Defined in domain so adapters and the web layer
// share one sentinel without coupling to each other.
var ErrVideoNotFound = errors.New("video not found")

// ErrChannelUnavailable is returned by a VideoSource when a channel's upload
// playlist returns HTTP 404 — the channel was deleted or made private. The runner
// stores these so the account page can surface them to the user.
type ErrChannelUnavailable struct {
	ChannelID    string
	ChannelTitle string
}

func (e *ErrChannelUnavailable) Error() string {
	return fmt.Sprintf("channel %q (%s) unavailable: playlist not found", e.ChannelTitle, e.ChannelID)
}

// Provider identifies a video platform.
type Provider string

const (
	ProviderYouTube Provider = "youtube"
	ProviderVimeo   Provider = "vimeo"
)

// TranscriptSource records how a transcript was obtained (or that there was none).
type TranscriptSource string

const (
	SourceCaptions TranscriptSource = "captions"
	SourceNone     TranscriptSource = "none"
	// SourceRateLimited records that the caption endpoint returned HTTP 429.
	// Unlike SourceNone (a permanent absence), this is a transient "retry later":
	// the IP is rate-limited, not the video caption-less. It carries no text
	// (HasText is false), so the engine degrades the same as SourceNone, but the
	// runner persists it distinctly to retry after a backoff window.
	SourceRateLimited TranscriptSource = "rate_limited"
)

// User is the Tapir-side profile. At Stage 0 there is exactly one.
type User struct {
	ID          string
	DisplayName string
	CreatedAt   time.Time
}

// Subscription is a watched channel on a connected account.
type Subscription struct {
	ID           string
	UserID       string
	ConnectionID string
	ChannelID    string
	ChannelTitle string
	Active       bool
}

// Video is a single video seen for a user (per-user, not globally deduped —
// see docs/data-model.md and DECISIONS.md "Rejected alternatives").
type Video struct {
	ID              string
	UserID          string
	SubscriptionID  string
	Provider        Provider
	ProviderVideoID string
	Title           string
	ChannelTitle    string
	URL             string
	PublishedAt     time.Time
	SeenAt          time.Time
	// DurationSeconds is the video length in seconds, when known (fetched by the
	// ADR-023 videos.list enrichment at discovery). 0 means unknown — the store
	// preserves a previously-known value rather than overwriting it with 0, and
	// the onboarding burst (ADR-028) treats unknown as degrade-open (kept).
	DurationSeconds int
}

// Transcript is the text of a video (or a record that none was available).
type Transcript struct {
	VideoID  string
	UserID   string
	Source   TranscriptSource
	Language string
	Content  string // empty when Source == SourceNone
}

// HasText reports whether the transcript carries usable text.
func (t Transcript) HasText() bool {
	return t.Source != SourceNone && t.Content != ""
}

// Summary is the produced output for a video.
type Summary struct {
	ID           string
	UserID       string
	VideoID      string
	Summary      string
	Highlights   []string
	Takeaways    []string
	AIProvider   string // "local" | "anthropic" | "openai" | "gemini"
	AIModel      string
	FallbackUsed bool
	CreatedAt    time.Time
}
