// Package changelog holds the hardcoded "What's New" feed.
//
// Entries are maintained in source — adding a new one is a struct append
// plus a deploy, no database migration, no admin UI. The list is kept
// newest-first by convention; ByDateDesc guarantees that ordering at
// runtime regardless of how authors insert new entries.
package changelog

import "sort"

// Tag categorises an entry for filtering and UI styling.
type Tag string

const (
	TagFeature Tag = "feature"
	TagContent Tag = "content"
	TagFix     Tag = "fix"
)

// Entry is one visible row in the changelog / one badge-worthy event.
type Entry struct {
	Date    string `json:"date"` // ISO 8601, YYYY-MM-DD
	TitleDE string `json:"title_de"`
	TitleEN string `json:"title_en"`
	BodyDE  string `json:"body_de"`
	BodyEN  string `json:"body_en"`
	Tag     Tag    `json:"tag"`
}

// Entries lists everything shipped so far, newest first. Append new rows
// at the top.
var Entries = []Entry{
	{
		Date:    "2026-04-30",
		Tag:     TagFeature,
		TitleDE: "Event-Typen für Fristen",
		TitleEN: "Event types for deadlines",
		BodyDE:  "Fristen lassen sich jetzt mit beliebigen Typen verschlagworten — etwa „SoD-Frist\" oder „Klageerwiderung\". Übersicht und Agenda haben einen Mehrfach-Filter nach Typ bekommen, neue Typen lassen sich beim Anlegen einer Frist direkt mit anlegen.",
		BodyEN:  "Deadlines can now be tagged with one or more event types — e.g. „SoD-Frist\" or „Klageerwiderung\". The deadlines list and the agenda gain a multi-select filter by type, and new types can be added inline while creating a deadline.",
	},
	{
		Date:    "2026-04-30",
		Tag:     TagFix,
		TitleDE: "UPC-Fristen genauer berechnet",
		TitleEN: "More accurate UPC deadlines",
		BodyDE:  "Mehrere Korrekturen am UPC-Fristenrechner: Mit Widerklage auf Nichtigkeit (CCR) wechselt die Replik-/Duplik-Frist automatisch auf 2 Monate (RoP 029.d). Das EP-Erteilungsdatum lässt sich an einem optionalen Prioritätstag verankern. Die Berufungsbegründung wird wieder direkt am Entscheidungsdatum gerechnet, und Fristen über die UPC-Sommerpause rutschen nicht mehr fälschlich auf einen Samstag.",
		BodyEN:  "Several fixes to the UPC deadline calculator: ticking „with counterclaim for revocation\" (CCR) flips the reply/rejoinder window to 2 months (RoP 029.d) automatically. EP grant publication can be anchored on an optional priority date. The appeal-grounds deadline is computed directly from the decision date again, and deadlines crossing the UPC summer vacation no longer land on a Saturday.",
	},
	{
		Date:    "2026-04-20",
		Tag:     TagFeature,
		TitleDE: "Einstellungen mit Benachrichtigungs-Präferenzen",
		TitleEN: "Settings with notification preferences",
		BodyDE:  "Unter Einstellungen lassen sich Profil, CalDAV-Zugang und E-Mail-Benachrichtigungen in drei Tabs verwalten. Fristen-Erinnerungen können pro Art (7 Tage / 1 Tag / überfällig) einzeln ein- oder ausgeschaltet werden.",
		BodyEN:  "Settings now bundles profile, CalDAV and email notifications in three tabs. Deadline reminders can be toggled per type (7 days / 1 day / overdue) independently.",
	},
	{
		Date:    "2026-04-18",
		Tag:     TagFeature,
		TitleDE: "Kolleg:innen per E-Mail einladen",
		TitleEN: "Invite colleagues by email",
		BodyDE:  "Die Schaltfläche „Kolleg:in einladen\" in der Seitenleiste öffnet ein Modal und verschickt einen Registrierungslink an eine HLC-E-Mail-Adresse. Einladungsstatus und Tageslimit werden direkt angezeigt.",
		BodyEN:  "The \"Invite colleague\" button in the sidebar opens a modal and sends a registration link to an HLC email address. Invitation status and daily quota are shown inline.",
	},
	{
		Date:    "2026-04-16",
		Tag:     TagContent,
		TitleDE: "Paliad — neuer Name, gleicher Lime",
		TitleEN: "Paliad — new name, same lime",
		BodyDE:  "Nach dem Zusammenschluss zu HLC wurde patHoLo zu Paliad. Ein firmenunabhängiger Name, damit die Plattform auch künftige Umbenennungen überlebt. Alte Bookmarks auf patholo.de leiten automatisch weiter.",
		BodyEN:  "Following the HLC merger, patHoLo became Paliad. A firm-agnostic name so the platform outlives future renames. Old patholo.de bookmarks redirect automatically.",
	},
	{
		Date:    "2026-04-15",
		Tag:     TagFeature,
		TitleDE: "Dashboard als Startseite",
		TitleEN: "Dashboard as landing page",
		BodyDE:  "Nach dem Login landen Sie jetzt auf einem Dashboard mit Fristen-Ampel, nächsten Terminen und letzter Aktivität — alles gefiltert auf Projekte, die Sie sehen dürfen. Server-gerendert, kein Warten auf Daten.",
		BodyEN:  "After login you now land on a dashboard with deadline traffic lights, upcoming appointments and recent activity — all scoped to projects you can see. Server-rendered, no data waterfall.",
	},
	{
		Date:    "2026-04-12",
		Tag:     TagFeature,
		TitleDE: "Termine + CalDAV-Sync",
		TitleEN: "Appointments + CalDAV sync",
		BodyDE:  "Termine lassen sich mit Projekten verknüpfen und optional in den persönlichen Kalender synchronisieren (iCloud, Google, eigener CalDAV-Server). Passwörter werden AES-GCM-verschlüsselt gespeichert.",
		BodyEN:  "Appointments can be linked to projects and optionally synced to your personal calendar (iCloud, Google, self-hosted CalDAV). Passwords are stored AES-GCM encrypted.",
	},
	{
		Date:    "2026-04-08",
		Tag:     TagFeature,
		TitleDE: "Fristen-Verwaltung",
		TitleEN: "Deadline management",
		BodyDE:  "Persistente Fristen mit Ampel-Karten, Kalenderansicht und E-Mail-Erinnerungen. Verknüpft mit Projekten, teilbar im Team, abhakbar per Klick.",
		BodyEN:  "Persistent deadlines with traffic-light cards, a calendar view and email reminders. Linked to projects, shared with the team, completable in one click.",
	},
	{
		Date:    "2026-04-04",
		Tag:     TagFeature,
		TitleDE: "Projekte mit Hierarchie",
		TitleEN: "Hierarchical projects",
		BodyDE:  "Projekte in vier Ebenen: Mandant → Streit → Patent → Akte. Team-Sichtbarkeit vererbt sich entlang des Baums — ein Team am obersten Knoten sieht alle Unterknoten automatisch.",
		BodyEN:  "Projects in four levels: Client → Litigation → Patent → Matter. Team visibility is inherited down the tree — a team on the top node automatically sees every descendant.",
	},
	{
		Date:    "2026-03-28",
		Tag:     TagFeature,
		TitleDE: "Checklisten v2 — interaktiv",
		TitleEN: "Checklists v2 — interactive",
		BodyDE:  "Einreichungs-Checklisten lassen sich jetzt als Instanz pro Akte durchklicken, Fortschritt wird gespeichert, Rücksetzen jederzeit möglich.",
		BodyEN:  "Filing checklists can now be run as per-matter instances, with saved progress and a reset option.",
	},
	{
		Date:    "2026-03-20",
		Tag:     TagContent,
		TitleDE: "Gerichtsverzeichnis",
		TitleEN: "Court directory",
		BodyDE:  "Kontaktdaten, Einreichungshinweise und Praxistipps zu UPC-Kammern, deutschen Patentgerichten und dem EPA — durchsuchbar und filterbar nach Typ und Land.",
		BodyEN:  "Contact details, filing notes and practical hints for UPC divisions, German patent courts and the EPO — searchable and filterable by type and country.",
	},
	{
		Date:    "2026-03-10",
		Tag:     TagContent,
		TitleDE: "Patentglossar DE/EN",
		TitleEN: "Patent glossary DE/EN",
		BodyDE:  "Über 80 Fachbegriffe aus Litigation, Prosecution, UPC, EPA und SEP/FRAND — zweisprachig mit Definitionen. Fehlende Begriffe lassen sich direkt vorschlagen.",
		BodyEN:  "Over 80 terms from litigation, prosecution, UPC, EPO and SEP/FRAND — bilingual with definitions. Missing terms can be suggested inline.",
	},
}

// All returns a copy of the entry list sorted newest-first by Date.
// Returning a copy keeps the package-level slice immutable for callers
// that might otherwise sort it in-place.
func All() []Entry {
	out := make([]Entry, len(Entries))
	copy(out, Entries)
	sort.SliceStable(out, func(i, j int) bool {
		return out[i].Date > out[j].Date
	})
	return out
}

// UnseenCount returns how many entries have a Date strictly greater than
// the given cutoff (ISO 8601, YYYY-MM-DD). An empty cutoff is treated as
// "never seen" — every entry counts.
func UnseenCount(since string) int {
	n := 0
	for _, e := range Entries {
		if e.Date > since {
			n++
		}
	}
	return n
}
