package services

// Application-layer mirror of paliad.can_see_project (defined in migration
// 023). The SQL function is enforced by RLS, but services run with a
// service-role connection that has no auth.uid() — so each query that
// returns project-scoped rows re-applies the same predicate inline.
//
// Predicate shape (matches can_see_project body):
//
//   global_admin in paliad.users  OR  any (direct or ancestor) team membership
//
// We resolve the global-admin shortcut by EXISTS-querying paliad.users on
// the supplied userID rather than asking callers to pass user.GlobalRole as
// a separate parameter. That eliminates an entire bug class (mismatched
// role string, e.g. checking 'admin' when the column stores 'global_admin')
// and means the only argument the predicate needs is the user UUID.
//
// Callers must JOIN paliad.projects under the given alias so the path-walk
// half of the predicate can read alias.path.

import "fmt"

// visibilityPredicate returns the predicate using sqlx named bind variables.
// Caller binds :user_id to the requesting user's UUID.
//
// Uses CAST(... AS uuid[]) — not the `::uuid[]` shorthand — because sqlx
// v1.4.0's named-parameter parser strips one colon from `::uuid[]` while
// compiling `:user_id` placeholders, producing invalid SQL `:uuid[]` that
// Postgres rejects with `syntax error at or near ":"`.
func visibilityPredicate(alias string) string {
	return `(EXISTS (
	    SELECT 1 FROM paliad.users u
	     WHERE u.id = :user_id AND u.global_role = 'global_admin'
	) OR EXISTS (
	    SELECT 1 FROM paliad.project_teams pt
	     WHERE pt.user_id = :user_id
	       AND pt.project_id = ANY(CAST(string_to_array(` + alias + `.path, '.') AS uuid[]))
	))`
}

// visibilityPredicatePositional returns the predicate with one positional
// placeholder ($userArg) for the user UUID. Use when the surrounding query
// can't take named parameters (mixed positional args, lib/pq array binding,
// etc.). The same $userArg is referenced twice — that is a feature of
// Postgres positional binding, not a bug.
func visibilityPredicatePositional(alias string, userArg int) string {
	return fmt.Sprintf(`(EXISTS (
	    SELECT 1 FROM paliad.users u
	     WHERE u.id = $%d AND u.global_role = 'global_admin'
	) OR EXISTS (
	    SELECT 1 FROM paliad.project_teams pt
	     WHERE pt.user_id = $%d
	       AND pt.project_id = ANY(string_to_array(%s.path, '.')::uuid[])
	))`, userArg, userArg, alias)
}

// projectDescendantPredicate matches rows whose joined `paliad.projects`
// row (under `alias`) is the project bound to :project_id OR any descendant
// of it. The check inspects the materialised path column on paliad.projects
// — :project_id appears in the path of every descendant, plus the project
// itself (since path always includes self as the last label, t-paliad-018).
//
// Uses CAST(... AS uuid[]) — not the `::uuid[]` shorthand — for the same
// sqlx-named-parameter reason called out on visibilityPredicate above.
//
// Caller must JOIN paliad.projects under the given alias and bind
// :project_id to the selected project's UUID.
func projectDescendantPredicate(alias string) string {
	return `:project_id = ANY(CAST(string_to_array(` + alias + `.path, '.') AS uuid[]))`
}
