package services

// SystemView is a code-resident view definition. The four system pages
// (dashboard / agenda / events / inbox) resolve to one of these when
// they want to consume the substrate as if they were a Custom View.
//
// Design: docs/design-data-display-model-2026-05-06.md §5 Q8.
//
// Q8 lock-in: defaults are config-as-code, not seeded rows in
// paliad.user_views. Their slugs are reserved (validator rejects
// matching user-view slugs).

import (
	"slices"
)

// SystemView is the in-process projection used by the substrate's
// SystemView callers. It mirrors the persisted user-view shape but
// never round-trips through the DB.
type SystemView struct {
	Slug   string     // matches the system-page URL ("/dashboard" → "dashboard")
	Name   string     // display label (kept English here; UI re-translates via i18n)
	Filter FilterSpec // canonical filter the page resolves to today
	Render RenderSpec // canonical render shape
}

// DashboardSystemView returns the SystemView definition for /dashboard.
//
// Note: /dashboard is composed of multiple sections (5-bucket summary +
// matter card + two-column lists + activity feed). It does NOT resolve
// to a single FilterSpec/RenderSpec — Phase B will compose several
// SystemView resolutions into the dashboard page. This entry exists so
// the slug is known to the reserved-list and so future composition has
// a stable hook.
func DashboardSystemView() SystemView {
	return SystemView{
		Slug: "dashboard",
		Name: "Dashboard",
		// Placeholder filter — the dashboard composes multiple queries
		// in Phase B; this single spec covers the activity feed only.
		Filter: FilterSpec{
			Version: SpecVersion,
			Sources: []DataSource{SourceProjectEvent},
			Scope:   ScopeSpec{Projects: ScopeProjects{Mode: ScopeAllVisible}},
			Time:    TimeSpec{Horizon: HorizonPast30d, Field: FieldCreatedAt},
		},
		Render: RenderSpec{
			Shape: ShapeList,
			List: &ListConfig{
				Density: DensityCompact,
				Sort:    SortDateDesc,
				Columns: []string{"time", "actor", "title", "project"},
			},
		},
	}
}

// AgendaSystemView returns the SystemView definition for /agenda — a
// day-grouped feed of upcoming deadlines + appointments.
func AgendaSystemView() SystemView {
	return SystemView{
		Slug: "agenda",
		Name: "Agenda",
		Filter: FilterSpec{
			Version: SpecVersion,
			Sources: []DataSource{SourceDeadline, SourceAppointment},
			Scope:   ScopeSpec{Projects: ScopeProjects{Mode: ScopeAllVisible}},
			Time:    TimeSpec{Horizon: HorizonNext30d, Field: FieldAuto},
			Predicates: map[DataSource]Predicates{
				SourceDeadline: {Deadline: &DeadlinePredicates{Status: []string{"pending"}}},
			},
		},
		Render: RenderSpec{
			Shape: ShapeCards,
			Cards: &CardsConfig{GroupBy: CardsGroupByDay, Sort: SortDateAsc},
		},
	}
}

// EventsSystemView returns the SystemView definition for /events — the
// table view over deadlines + appointments. The legacy URL keeps a
// per-type chip toggle; this SystemView reflects the "all" tab default.
func EventsSystemView() SystemView {
	return SystemView{
		Slug: "events",
		Name: "Events",
		Filter: FilterSpec{
			Version: SpecVersion,
			Sources: []DataSource{SourceDeadline, SourceAppointment},
			Scope:   ScopeSpec{Projects: ScopeProjects{Mode: ScopeAllVisible}},
			Time:    TimeSpec{Horizon: HorizonAny, Field: FieldAuto},
		},
		Render: RenderSpec{
			Shape: ShapeList,
			List: &ListConfig{
				Density: DensityComfortable,
				Sort:    SortDateAsc,
			},
		},
	}
}

// InboxSystemView returns the SystemView definition for /inbox — the
// 4-eye approval surface. The bar's approval_viewer_role chip
// cluster narrows to "Zur Genehmigung" / "Eigene Anfragen" /
// "Alle sichtbaren". Default is "any_visible" so the page lands on
// a populated view for every user (m's 2026-05-08 22:08 dogfood:
// "the inbox somehow does not show nothing no more" — the prior
// default was approver_eligible, which is empty for users who only
// SUBMIT requests and have nothing to approve themselves).
//
// RowAction = RowActionApprove → shape-list.ts renders the approval
// row layout (entity title + diff + approve/reject/revoke buttons)
// and the surface wires action handlers via the rendered data-attrs.
func InboxSystemView() SystemView {
	return SystemView{
		Slug: "inbox",
		Name: "Inbox",
		Filter: FilterSpec{
			Version: SpecVersion,
			Sources: []DataSource{SourceApprovalRequest},
			Scope:   ScopeSpec{Projects: ScopeProjects{Mode: ScopeAllVisible}},
			Time:    TimeSpec{Horizon: HorizonAny, Field: FieldAuto},
			Predicates: map[DataSource]Predicates{
				SourceApprovalRequest: {ApprovalRequest: &ApprovalRequestPredicates{
					ViewerRole: "any_visible",
					Status:     []string{"pending"},
				}},
			},
		},
		Render: RenderSpec{
			Shape: ShapeList,
			List: &ListConfig{
				Density:   DensityComfortable,
				Sort:      SortDateAsc,
				RowAction: RowActionApprove,
			},
		},
	}
}

// InboxRequesterSystemView is the "Eigene Anfragen" sibling view of
// /inbox. Reachable via the bar's approval_viewer_role chip ("Eigene
// Anfragen") on the /inbox surface, or as its own URL on /views/inbox-mine.
func InboxRequesterSystemView() SystemView {
	return SystemView{
		Slug: "inbox-mine",
		Name: "Inbox (mine)",
		Filter: FilterSpec{
			Version: SpecVersion,
			Sources: []DataSource{SourceApprovalRequest},
			Scope:   ScopeSpec{Projects: ScopeProjects{Mode: ScopeAllVisible}},
			Time:    TimeSpec{Horizon: HorizonAny, Field: FieldAuto},
			Predicates: map[DataSource]Predicates{
				SourceApprovalRequest: {ApprovalRequest: &ApprovalRequestPredicates{
					ViewerRole: "self_requested",
				}},
			},
		},
		Render: RenderSpec{
			Shape: ShapeList,
			List: &ListConfig{
				Density:   DensityComfortable,
				Sort:      SortDateAsc,
				RowAction: RowActionApprove,
			},
		},
	}
}

// AllSystemViews returns every system-defined view in registration order.
// Used by the reserved-slug list and by future Phase B composition.
func AllSystemViews() []SystemView {
	return []SystemView{
		DashboardSystemView(),
		AgendaSystemView(),
		EventsSystemView(),
		InboxSystemView(),
		InboxRequesterSystemView(),
	}
}

// reservedUserViewSlugs is the static list of slugs the user-view CRUD
// rejects on create / update. Includes the SystemView slugs plus URLs
// the application owns at the top level (admin, settings, login, …).
//
// Q23 lock-in (m, 2026-05-07): list as drafted.
var reservedUserViewSlugs = []string{
	// SystemView slugs:
	"dashboard", "agenda", "events", "inbox", "inbox-mine",
	// /views/* routes:
	"new", "edit",
	// Top-level application URLs:
	"tools", "admin", "settings", "login", "logout",
	"projects", "team", "courts", "glossary", "links",
	"downloads", "checklists", "views", "changelog",
}

// IsReservedUserViewSlug returns true if `slug` matches a reserved slug.
// User-view CRUD rejects matches with ErrInvalidInput. Case-folded so
// "Dashboard" is also rejected.
func IsReservedUserViewSlug(slug string) bool {
	return slices.Contains(reservedUserViewSlugs, foldSlug(slug))
}

// foldSlug normalises a slug for reserved-list comparison. Slugs are
// already lowercased + dash-only by the validator before this is called,
// but this lets IsReservedUserViewSlug be safe under direct calls.
func foldSlug(s string) string {
	out := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		c := s[i]
		switch {
		case c >= 'A' && c <= 'Z':
			out = append(out, c+('a'-'A'))
		default:
			out = append(out, c)
		}
	}
	return string(out)
}
