package ports

import (
	"context"
	"time"

	"github.com/nexuscrm/kernel/pkg/rls"
	"github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/shared/pkg/models"
)

// PermissionSetRef is a reference to a permission set within a group
type PermissionSetRef struct {
	ID      string
	APIName string
	Label   string
}

// RecordTypeCatalogEntry identifies an active record type and its owning
// object for permission-set record type access authoring.
type RecordTypeCatalogEntry struct {
	RecordTypeID  string
	ObjectAPIName string
	Name          string
	Label         string
	IsActive      bool
	IsMaster      bool
}

// RelatedListConfig represents related list configuration without database types
type RelatedListConfig struct {
	LookupFieldAPI    string
	LookupFieldLabel  string
	ChildObjectAPI    string
	ChildPluralLabel  string
	RelatedListLabel  *string // nil instead of sql.NullString
	RelatedListFields *string // nil instead of sql.NullString
}

// RecordRepository defines the interface for generic record persistence
type RecordRepository interface {
	// Exists checks if a record exists by ID
	Exists(ctx context.Context, tableName string, id string) (bool, error)

	// GetLock retrieves a record by ID and locks it (SELECT ... FOR UPDATE)
	GetLock(ctx context.Context, tableName string, id string) (models.SObject, error)

	// Insert executes an INSERT statement
	Insert(ctx context.Context, tableName string, record models.SObject) error

	// BulkInsert executes a multi-row INSERT statement for batch insertions
	BulkInsert(ctx context.Context, tableName string, records []models.SObject, allColumns []string, options interface{}) error

	// BulkInsertWithLanding executes bulk insert and reports, per input
	// record, whether that record was actually inserted. This is required for
	// callers that run post-insert side effects under INSERT IGNORE, where a
	// duplicate row is a success but must not trigger side effects.
	BulkInsertWithLanding(ctx context.Context, tableName string, records []models.SObject, allColumns []string, options interface{}) ([]bool, error)

	// Update executes an UPDATE statement
	Update(ctx context.Context, tableName string, id string, updates models.SObject) error

	// Delete executes a DELETE statement
	Delete(ctx context.Context, tableName string, id string) error

	// GetChildren retrieves active child records for cascade delete
	GetChildren(ctx context.Context, childTable string, foreignKey string, parentID string) ([]models.SObject, error)

	// GetDeletedChildren retrieves soft-deleted child records for recycle-bin
	// purge traversal.
	GetDeletedChildren(ctx context.Context, childTable string, foreignKey string, parentID string) ([]models.SObject, error)

	// ExistsByField checks if any record exists matching a specific field value
	ExistsByField(ctx context.Context, tableName string, fieldName string, value interface{}) (bool, error)

	// FindOne returns a single record by ID, excluding deleted
	FindOne(ctx context.Context, tableName string, id string) (models.SObject, error)

	// FindMany returns multiple records by IDs, excluding deleted. Returns a map of ID -> record.
	FindMany(ctx context.Context, tableName string, ids []string) (map[string]models.SObject, error)

	// FindOneByField returns a single record matching a specific field value, excluding deleted
	FindOneByField(ctx context.Context, tableName string, fieldName string, value interface{}) (models.SObject, error)

	// FindAny returns a record by ID, INCLUDING deleted ones
	FindAny(ctx context.Context, tableName string, id string) (models.SObject, error)

	// PhysicalDelete permanently removes a record
	PhysicalDelete(ctx context.Context, tableName string, id string) error

	// DeleteByField deletes records matching a specific field value
	DeleteByField(ctx context.Context, tableName string, fieldName string, value interface{}) error

	// CheckUniqueness checks if a value exists for a field, excluding a specific ID
	CheckUniqueness(ctx context.Context, schema *models.ObjectMetadata, fieldName string, value interface{}, excludeID string) (bool, error)

	// FindAllRecycleBinItems retrieves all recycle bin items (Admin only typically)
	FindAllRecycleBinItems(ctx context.Context) ([]models.SObject, error)

	// FindRecycleBinItemsByUserPage retrieves one keyset page of recycle bin
	// items deleted by a specific user, matched on the stable DeletedById (no
	// mutable display-name read path). Legacy DeletedById-IS-NULL tombstones
	// (written before the column existed) have no authoritative delete-time
	// identity and are excluded from scope=mine — visible only via scope=all.
	// See pingcap-inc/NexusCRM#1750.
	FindRecycleBinItemsByUserPage(ctx context.Context, deleterID string, afterDeletedDate string, afterID string, limit int) ([]models.SObject, error)

	// FindAllRecycleBinItemsPage retrieves one keyset page of all recycle bin items.
	FindAllRecycleBinItemsPage(ctx context.Context, afterDeletedDate string, afterID string, limit int) ([]models.SObject, error)

	// FindRecycleBinEntry finds a recycle bin entry by original Record ID
	FindRecycleBinEntry(ctx context.Context, recordID string) (models.SObject, error)

	// CheckQueueSupport checks if a queue supports a specific object type
	CheckQueueSupport(ctx context.Context, queueID string, objectType string) (bool, error)

	// GetRecordType looks up the object type for a given record ID
	// This is used for operations like DELETE where the object type is unknown
	GetRecordType(ctx context.Context, id string) (string, error)

	// FindObjectTypesForIDs finds object types for a list of IDs.
	// The tableNames argument is legacy compatibility; implementations should
	// use their authoritative object-identity source rather than caller-provided
	// cached metadata.
	FindObjectTypesForIDs(ctx context.Context, ids []string, tableNames []string) (map[string]string, error)
}

// TransactionManager defines the interface for managing transactions
type TransactionManager interface {
	// WithTransaction executes a function within a database transaction
	WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error

	// WithPessimisticTransaction executes a function within a TiDB
	// pessimistic-mode transaction. The caller's fn receives a context
	// carrying the pinned transaction. Serialization is achieved by FOR
	// UPDATE locks inside fn; this wrapper ensures the session is in
	// pessimistic mode so those locks block rather than snapshot.
	// If the context already carries a pessimistic marker (nested call),
	// fn is executed directly without opening a second transaction.
	WithPessimisticTransaction(ctx context.Context, fn func(ctx context.Context) error) error

	// WithRetry executes a replay-safe function within a transaction with
	// automatic retry on lock contention and planned history-sequence prelocks.
	// Commit or rollback outcome ambiguity fails closed without replay.
	WithRetry(ctx context.Context, fn func(ctx context.Context) error, maxRetries int) error

	// WithPessimisticRetry executes replay-safe fn inside a pessimistic
	// transaction with automatic retry on lock contention (TiDB 1205/1213) and
	// planned history-sequence prelocks. Commit or rollback outcome ambiguity
	// fails closed without replay. If the context already carries a
	// pessimistic marker, fn is executed directly and does not claim replay
	// ownership from the enclosing transaction.
	WithPessimisticRetry(ctx context.Context, fn func(ctx context.Context) error, maxRetries int) error

	// IsTransactionActive checks if a transaction is currently active in the context
	IsTransactionActive(ctx context.Context) bool

	// IsPessimisticTx reports whether the context is inside a pessimistic
	// transaction. A context is safe for blocking SELECT ... FOR UPDATE only
	// when this returns true AND IsTransactionActive also returns true.
	IsPessimisticTx(ctx context.Context) bool

	// ExtractTx returns the underlying transaction object (as interface{}) from context
	ExtractTx(ctx context.Context) interface{}

	// DetachTx returns a context with no active transaction and no pessimistic
	// marker, so that callers using the returned context will open a fresh,
	// independent transaction instead of reusing the current one. Used by
	// automation steps that must write to the same row the enclosing
	// transaction holds a lock on. Detachment also strips transaction-scoped
	// replay capabilities and hook collectors.
	DetachTx(ctx context.Context) context.Context
}

// MetadataRepository defines the interface for metadata persistence
type MetadataRepository interface {
	// Schema Operations
	GetSchemaByAPIName(ctx context.Context, apiName string) (*models.ObjectMetadata, error)
	GetAllSchemas(ctx context.Context) ([]*models.ObjectMetadata, error)
	GetFieldsForObject(ctx context.Context, objectID string) ([]models.FieldMetadata, error)
	GetRecordTypes(ctx context.Context, objectAPIName string) ([]*models.RecordType, error)
	CreateOrReviveRecordType(ctx context.Context, objectAPIName, name string, description *string, isActive bool) (string, error)
	UpdateRecordType(ctx context.Context, id string, name *string, description *string, descriptionSet bool, isActive *bool) error
	DeleteRecordType(ctx context.Context, id string) error
	GetScopedPicklistValues(ctx context.Context, recordTypeID, fieldID string) ([]models.ScopedPicklistValue, error)
	GetAllRelationships(ctx context.Context) ([]*models.Relationship, error)
	GetRelationships(ctx context.Context, childObjectAPIName string) ([]*models.Relationship, error)
	GetFieldDependencies(ctx context.Context, objectAPIName string) ([]*models.FieldDependency, error)
	GetChildRelationships(ctx context.Context, parentObjectAPIName string) ([]*models.ObjectMetadata, error)

	// ConfigureAutoNumberField writes the autonumber v2 format + scope +
	// start onto `_System_Field.auto_number_*` for (objectAPIName,
	// fieldAPIName) and seeds the corresponding `_System_AutoNumberCounter`
	// row (scope=global). Replaces the deleted UpsertAutoNumber which
	// wrote to the retired `_System_AutoNumber` table. Idempotent.
	ConfigureAutoNumberField(ctx context.Context, objectAPIName, fieldAPIName, format string, start int64) error

	// UI & Layouts
	UpsertUIComponent(ctx context.Context, component *models.UIComponent) error
	UpsertSetupPage(ctx context.Context, page *models.SetupPage) error
	GetSetupPages(ctx context.Context) ([]*models.SetupPage, error)
	UpsertLayout(ctx context.Context, layout *models.PageLayout) error
	SaveLayout(ctx context.Context, layout *models.PageLayout) error
	GetLayout(ctx context.Context, id string) (*models.PageLayout, error)
	GetLayouts(ctx context.Context, objectAPIName string) ([]*models.PageLayout, error)
	DeleteLayout(ctx context.Context, id string) error
	SetDefaultLayout(ctx context.Context, objectAPIName string, layoutID string, actorID *string) error
	AssignLayoutToProfile(ctx context.Context, profileID, objectAPIName, layoutID string) error
	GetLayoutIDForProfile(ctx context.Context, objectAPIName, profileID string) (string, error)
	GetRelatedListConfigs(ctx context.Context, layoutObjectAPIName string) ([]RelatedListConfig, error)

	// Approval Layouts
	GetApprovalLayouts(ctx context.Context, objectAPIName string) ([]*models.ApprovalPageLayout, error)
	GetApprovalLayout(ctx context.Context, id string) (*models.ApprovalPageLayout, error)
	SaveApprovalLayout(ctx context.Context, layout *models.ApprovalPageLayout) error
	DeleteApprovalLayout(ctx context.Context, id string) error
	SetDefaultApprovalLayout(ctx context.Context, objectAPIName string, layoutID string) error

	// Layout Assignment (new specificity-based routing)
	FindBestAssignment(ctx context.Context, objectAPIName string, roleAncestors []string, profileID string, recordTypeID string) (*models.LayoutAssignment, error)
	CreateAssignment(ctx context.Context, assignment *models.LayoutAssignment) error
	DeleteAssignment(ctx context.Context, id string) error
	GetAssignmentsForObject(ctx context.Context, objectAPIName string) ([]*models.LayoutAssignment, error)
	DeleteAssignmentsByLayoutID(ctx context.Context, layoutID string) error

	// Layout History
	WriteLayoutHistory(ctx context.Context, entry *models.PageLayoutHistoryEntry) error
	GetLayoutHistoryMaxVersion(ctx context.Context, layoutID string) (int, error)
	GetLayoutHistoryConfig(ctx context.Context, layoutID string, versionNumber int) (string, error)
	PruneLayoutHistory(ctx context.Context, layoutID string, keepAboveVersion int) error

	// Migration
	GetMigrationVersion(ctx context.Context, migrationID string) (int, error)
	SetMigrationVersion(ctx context.Context, migrationID string, version int, description string) error

	// Themes
	GetActiveTheme(ctx context.Context) (*models.Theme, error)
	GetThemeByName(ctx context.Context, name string) (*models.Theme, error)
	CreateTheme(ctx context.Context, theme *models.Theme) error
	UpdateTheme(ctx context.Context, theme *models.Theme) error
	ActivateTheme(ctx context.Context, themeID string) error

	// Apps
	GetAllApps(ctx context.Context) ([]*models.AppConfig, error)
	GetApp(ctx context.Context, id string) (*models.AppConfig, error)
	GetAppWithTx(ctx context.Context, tx interface{}, id string) (*models.AppConfig, error)
	CreateApp(ctx context.Context, app *models.AppConfig) error
	CreateAppMenuItem(ctx context.Context, app *models.AppConfig) error
	UpdateApp(ctx context.Context, id string, app *models.AppConfig) error
	UpdateAppWithTx(ctx context.Context, tx interface{}, id string, app *models.AppConfig) error
	DeleteApp(ctx context.Context, id string) error

	// Dashboards
	GetAllDashboards(ctx context.Context) ([]*models.DashboardConfig, error)
	GetDashboard(ctx context.Context, id string) (*models.DashboardConfig, error)
	CreateDashboard(ctx context.Context, dashboard *models.DashboardConfig) error
	UpdateDashboard(ctx context.Context, id string, dashboard *models.DashboardConfig) error
	DeleteDashboard(ctx context.Context, id string) error

	// List Views
	GetListViews(ctx context.Context, objectAPIName string) ([]*models.ListView, error)

	// Actions & Sharing
	GetActions(ctx context.Context, objectAPIName string) ([]*models.ActionMetadata, error)
	GetAction(ctx context.Context, id string) (*models.ActionMetadata, error)
	GetLiveAction(ctx context.Context, id string) (*models.ActionMetadata, error)
	GetAllActions(ctx context.Context) ([]*models.ActionMetadata, error)
	CreateAction(ctx context.Context, action *models.ActionMetadata) error
	UpdateAction(ctx context.Context, actionID string, updates *models.ActionMetadata) error
	DeleteAction(ctx context.Context, actionID string) error
	CheckActionExists(ctx context.Context, objectAPIName, name string) (bool, error)
	GetSharingRules(ctx context.Context, objectAPIName string) ([]*models.SharingRule, error)

	// FlowDefinition CRUD (Salesforce-compatible)
	GetAllFlowDefinitions(ctx context.Context) ([]*models.FlowDefinition, error)
	GetFlowDefinition(ctx context.Context, id string) (*models.FlowDefinition, error)
	GetFlowDefinitionByName(ctx context.Context, developerName string) (*models.FlowDefinition, error)
	CreateFlowDefinition(ctx context.Context, def *models.FlowDefinition) error
	// LockFlowDefinitionForUpdate acquires a pessimistic FOR UPDATE lock on
	// the FlowDefinition row by Id. Returns *NotFoundError if the row does
	// not exist. Used by concurrent-write sites to serialize on the
	// DeveloperName-axis before reading-max-then-INSERTing Flow versions.
	LockFlowDefinitionForUpdate(ctx context.Context, definitionId string) error
	// LockFlowDefinitionByDeveloperNameForUpdate acquires a pessimistic
	// FOR UPDATE lock and returns the full authoritative FlowDefinition row
	// by DeveloperName.
	LockFlowDefinitionByDeveloperNameForUpdate(ctx context.Context, developerName string) (*models.FlowDefinition, error)
	// EnsureFlowDefinitionByDeveloperName performs an INSERT IGNORE stake-out
	// followed by a SELECT FOR UPDATE to guarantee exactly one
	// FlowDefinition row for the given DeveloperName. Returns the locked
	// authoritative row (either the input row for a first-create, or the
	// existing row for a collision). Used by tenant-overlay first-create to
	// prevent concurrent INSERT races on FlowDefinition.UNIQUE(DeveloperName).
	EnsureFlowDefinitionByDeveloperName(ctx context.Context, def *models.FlowDefinition) (*models.FlowDefinition, error)
	UpdateFlowDefinition(ctx context.Context, id string, def *models.FlowDefinition) error
	PatchFlowDefinition(ctx context.Context, id string, patch *models.FlowDefinitionPatch) error
	MarkFlowDefinitionCustomized(ctx context.Context, id string) error
	SetLatestVersionId(ctx context.Context, definitionId string, versionId string) error
	SetTriggerInfo(ctx context.Context, definitionId string, triggerType string, objectOrEvent string) error
	DeleteFlowDefinition(ctx context.Context, id string) error

	// FlowVersion CRUD (Salesforce-compatible)
	GetFlowVersions(ctx context.Context, definitionId string) ([]*models.FlowVersion, error)
	// GetFlowVersionsForUpdate returns all versions for a definition using a
	// SELECT ... FOR UPDATE current read. Callers must already hold the parent
	// FlowDefinition lock when using this to allocate a new VersionNumber.
	GetFlowVersionsForUpdate(ctx context.Context, definitionId string) ([]*models.FlowVersion, error)
	GetFlowVersion(ctx context.Context, id string) (*models.FlowVersion, error)
	GetFlowVersionsByIDs(ctx context.Context, ids []string) (map[string]*models.FlowVersion, error)
	GetActiveFlowVersion(ctx context.Context, definitionId string) (*models.FlowVersion, error)
	CreateFlowVersion(ctx context.Context, version *models.FlowVersion) error
	CreateSeededFlowVersion(ctx context.Context, version *models.FlowVersion) error
	CreateTenantFlowVersion(ctx context.Context, version *models.FlowVersion) error
	UpdateFlowVersion(ctx context.Context, id string, version *models.FlowVersion) error
	ActivateFlowVersion(ctx context.Context, id string) error
	DeactivateFlowVersion(ctx context.Context, id string) error
	DeleteFlowVersion(ctx context.Context, id string) error

	// FlowInterview CRUD (Salesforce-compatible)
	GetFlowInterviews(ctx context.Context) ([]*models.FlowInterview, error)
	GetFlowInterview(ctx context.Context, id string) (*models.FlowInterview, error)
	SaveFlowInterview(ctx context.Context, interview *models.FlowInterview) error
	DeleteFlowInterview(ctx context.Context, id string) error

	// Validation Rules
	GetAllValidationRules(ctx context.Context) ([]*models.ValidationRule, error)
	GetValidationRules(ctx context.Context, objectAPIName string) ([]*models.ValidationRule, error)
	GetValidationRule(ctx context.Context, id string) (*models.ValidationRule, error)
	CreateValidationRule(ctx context.Context, rule *models.ValidationRule) error
	UpdateValidationRule(ctx context.Context, id string, updates *models.ValidationRule) error
	DeleteValidationRule(ctx context.Context, id string) error
	CheckValidationRuleExists(ctx context.Context, objectAPIName, name string) (bool, error)
}

// SchemaRepository defines the interface for schema DDL and registry operations
type SchemaRepository interface {
	CreatePhysicalTable(ctx context.Context, def schema.TableDefinition) error
	CreateTableWithStrictMetadata(ctx context.Context, def schema.TableDefinition, objectMeta *models.ObjectMetadata) error
	BatchCreatePhysicalTables(ctx context.Context, defs []schema.TableDefinition) error
	DropTable(tableName string) error
	ValidateSchema(tableName string) error
	ValidateFieldDefinition(field schema.ColumnDefinition) error
	ValidateSchemaRegistry() (*schema.SchemaHealth, error)
	BatchRegisterTables(defs []schema.TableDefinition, tx interface{}) error
	AddColumn(tableName string, col schema.ColumnDefinition) error
	RegisterFieldOnly(tableName string, col schema.ColumnDefinition) error
	EnsureColumn(tableName string, col schema.ColumnDefinition) error
	// EnsureColumnIndex idempotently indexes a single column (used to keep
	// relationship FK columns indexed). No-op if already indexed.
	EnsureColumnIndex(tableName string, columnName string) error
	ResolvePackOwnedFieldConvergence(ctx context.Context, objectAPIName string, field schema.FieldWithContext) (schema.PackOwnedFieldConvergencePlan, error)
	// EnsurePackOwnedField is disabled-history admission/convergence only;
	// TrackHistory transitions require an active-transaction exact owner.
	EnsurePackOwnedField(ctx context.Context, objectAPIName string, field schema.FieldWithContext) error
	DropColumn(tableName string, colName string) error
	DropColumnPhase1(tableName string, colName string, expectedOwnerPack string) error
	DropColumnPhase2(tableName string, colName string, expectedOwnerPack string) error
	ModifyColumn(tableName, colName string, col schema.ColumnDefinition) error
	IsSystemColumn(name string) bool
	GetStandardSystemColumns() []schema.ColumnDefinition
	GetStandardFieldMetadata() []models.FieldMetadata
	SaveObjectMetadata(ctx context.Context, obj *models.ObjectMetadata, tx interface{}) error
	InsertObjectMetadata(ctx context.Context, obj *models.ObjectMetadata, tx interface{}) error
	BatchSaveObjectMetadata(ctx context.Context, objs []*models.ObjectMetadata, tx interface{}) error
	SaveFieldMetadataWithIDs(ctx context.Context, field *models.FieldMetadata, objectID string, objectAPIName string, fieldID string, tx interface{}) error
	BatchSaveFieldMetadata(ctx context.Context, fields []schema.FieldWithContext, tx interface{}) error
	PrepareFieldForBatch(tableName string, col schema.ColumnDefinition) schema.FieldWithContext
	GetTableRegistry(ctx context.Context) ([]*schema.TableRegistryItem, error)
	MapFieldTypeToSQL(fieldType string) string
	ValidateFormula(formulaStr string, env map[string]interface{}) error
	BatchAddColumns(tableName string, fields []models.FieldMetadata) error
}

// RollupRepository defines the interface for rollup calculations
type RollupRepository interface {
	CalculateRollup(ctx context.Context, calcType, summaryObject, summaryField, relationField, parentID string, filter *string) (interface{}, error)
	CalculateRollupCurrentRead(ctx context.Context, calcType, summaryObject, summaryField, relationField, parentID string, filter *string) (interface{}, error)
	RecordMatchesRollupFilter(ctx context.Context, summaryObject, recordID string, filter *string) (bool, error)
	GetParentIDsPage(ctx context.Context, objectName string, afterID string, limit int) ([]string, error)
}

// RollupFilterBatchRepository is the optional, semantics-preserving batch
// surface for evaluating several rollup filters against one immutable record
// image. Implementations return results in the same order as filters. Keeping
// this separate from RollupRepository preserves compatibility with narrow test
// and product adapters while allowing the production SQL owner to collapse N
// remote round trips into one conditional-aggregate read.
type RollupFilterBatchRepository interface {
	RecordMatchesRollupFilters(ctx context.Context, summaryObject, recordID string, filters []*string) ([]bool, error)
}

// Helper for type assertion compilation check
// var _ RecordRepository = (*persistence.RecordRepository)(nil)

// SystemRepository defines the interface for system-level operations
type SystemRepository interface {
	GetLogs(ctx context.Context, limit int) ([]*models.SystemLog, error)
	GetRecentItems(ctx context.Context, userID string, limit int) ([]*models.SystemRecent, error)
	GetConfig(ctx context.Context, key string) (*string, error)
	GetAllConfigs(ctx context.Context) ([]*models.SystemConfig, error)
	GetConfigID(ctx context.Context, key string) (string, error)
	UpsertConfig(ctx context.Context, config *models.SystemConfig) error
	DeleteConfig(ctx context.Context, key string) error
	GetProfileIDByName(ctx context.Context, name string) (string, error)
	GetUserIDByEmail(ctx context.Context, email string) (string, error)
	BatchUpsertProfiles(ctx context.Context, profiles []models.Profile) error
	BatchUpsertUsers(ctx context.Context, users []models.User) error
	ForceUpdateUserPassword(ctx context.Context, email string, hashedPassword string) error
	GetConnectedAppByClientID(ctx context.Context, clientID string) (*models.SystemConnectedApp, error)
	IsUserPermittedForApp(ctx context.Context, appID string, userID string) (bool, error)
	// WithConnectedAppCredentialIssuance serializes one complete credential
	// issuance with Connected App policy/access mutation. The callback must
	// re-read policy and perform every credential write through the supplied
	// context so authorization and issuance commit atomically.
	WithConnectedAppCredentialIssuance(ctx context.Context, appID string, issue func(context.Context) error) error
}

// OutboxRepository defines the interface for outbox event storage
type OutboxRepository interface {
	Enqueue(ctx context.Context, eventType string, payload interface{}) (string, error)
	GetPendingEvents(ctx context.Context, limit int) ([]models.OutboxEvent, error)
	GetPendingEventsWithRecordFIFO(ctx context.Context, limit int) ([]models.OutboxEvent, error)
	ClaimEvent(ctx context.Context, id string) (models.OutboxClaim, error)
	CreateOutboxAttemptSavepoint(ctx context.Context) error
	RollbackToOutboxAttemptSavepoint(ctx context.Context) error
	ReleaseOutboxAttemptSavepoint(ctx context.Context) error
	// RefreshOutboxProcessingClaim atomically proves a scoped claim's token,
	// scope, status, and database-owned live lease, then renews and locks it.
	// ErrOutboxEventAlreadyClaimed means the attempt no longer has authority.
	RefreshOutboxProcessingClaim(ctx context.Context, claim models.OutboxClaim) error
	UpdateStatus(ctx context.Context, claim models.OutboxClaim, status string, errMessage string) error
	// MarkFailed terminalizes one failed attempt and atomically increments the
	// durable count. Runtime poison repair compares that count with the observed
	// error, so no caller-supplied discovery value may become receipt authority.
	MarkFailed(ctx context.Context, claim models.OutboxClaim, errMessage string) error
	// IncrementRetry returns one failed attempt to pending and atomically
	// increments the same durable count.
	IncrementRetry(ctx context.Context, claim models.OutboxClaim, errMessage string) error
	CleanupProcessed(ctx context.Context, cutoff time.Time) (int64, error)
}

// UserDeactivationHook is called when a user is deactivated or deleted.
// Implementations should revoke sessions and invalidate caches.
type UserDeactivationHook interface {
	OnUserDeactivated(userID string)
}

// ConnectedAppInvalidationHook is called when Connected App security metadata
// changes in a way that can affect OAuth session validity.
type ConnectedAppInvalidationHook interface {
	OnConnectedAppChanged(appID string)
}

// SessionRepository defines the interface for user session management
type SessionRepository interface {
	InsertSession(ctx context.Context, session *models.SystemSession) error
	GetSession(ctx context.Context, sessionID string) (*models.SystemSession, error)
	AdvanceSessionAuthEpoch(ctx context.Context, sessionID string, expectedEpoch uint64) (uint64, error)
	RevokeSession(ctx context.Context, sessionID, expectedToken string) (bool, error)
	RevokeAllSessionsForUser(ctx context.Context, userID string) error
	// RevokeAllSessionsByProfileID revokes every active session whose user is
	// currently assigned to the given Profile. Used as the "compress staleness
	// window" mechanism when an admin renames a Profile or otherwise mutates
	// Profile-bound facts that flow into session-bound formula globals
	// ($Profile.Name). Returns rows affected.
	RevokeAllSessionsByProfileID(ctx context.Context, profileID string) (int64, error)
	// RevokeAllSessionsByUserRoleID revokes every active session whose user is
	// currently assigned to the given UserRole. Same rationale as
	// RevokeAllSessionsByProfileID, applied to $UserRole-bound facts.
	RevokeAllSessionsByUserRoleID(ctx context.Context, roleID string) (int64, error)
	RevokeAllSessionsForConnectedApp(ctx context.Context, appID string) (int64, error)
	// RevokeSessionsForUserAndConnectedApp revokes only the caller's sessions
	// issued for one authorization client. It is the eager cleanup part of a
	// user-initiated OAuth disconnect and must honor a context transaction.
	RevokeSessionsForUserAndConnectedApp(ctx context.Context, userID, appID string) (int64, error)
	RenewSession(ctx context.Context, sessionID, previousToken, token string, expiresAt, now time.Time) (bool, error)
	UpdateLastActivity(ctx context.Context, sessionID string) error
	CleanupExpiredSessions(ctx context.Context, olderThan time.Duration) (int64, error)
	IsConnectedAppActive(ctx context.Context, appID string) (bool, error)
}

// ApiTokenRepository defines persistence for self-service personal access
// tokens (_System_ApiToken). Tokens are stored only as a hash; the plaintext
// value is shown to the owner once at creation and never persisted.
type ApiTokenRepository interface {
	Insert(ctx context.Context, token *models.SystemApiToken) error
	// GetByHash returns the token row whose TokenHash matches, or (nil, nil) if
	// none exists. Revocation/expiry are not filtered here so the caller can
	// distinguish the reasons a token is rejected.
	GetByHash(ctx context.Context, tokenHash string) (*models.SystemApiToken, error)
	ListByOwner(ctx context.Context, ownerID string) ([]models.SystemApiToken, error)
	// Revoke marks one token revoked, scoped to its owner. Returns true when a
	// still-active row was revoked, false when nothing matched.
	Revoke(ctx context.Context, id, ownerID string) (bool, error)
	// RevokeAllNamedForOwnerExcept marks every still-active token of ownerID
	// whose Name equals name revoked in ONE statement, keeping keepID (pass
	// "" to revoke all matching). Returns the number of tokens revoked. The
	// single-statement form exists so token rotation has an atomic bulk step
	// instead of a partial-failure-prone per-token loop; the name filter
	// scopes rotation to one credential family so rotating it can never
	// revoke the owner's unrelated tokens.
	RevokeAllNamedForOwnerExcept(ctx context.Context, ownerID, name, keepID string) (int64, error)
	TouchLastUsed(ctx context.Context, id string) error
}

// PermissionRepository defines the interface for permission management
type PermissionRepository interface {
	LoadObjectPermission(ctx context.Context, profileID, objectAPIName string) (*models.ObjectPermissions, error)
	LoadFieldPermission(ctx context.Context, profileID, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error)
	LoadEffectiveObjectPermission(ctx context.Context, user *models.UserSession, objectAPIName string) (*models.ObjectPermissions, error)
	LoadEffectiveFieldPermission(ctx context.Context, user *models.UserSession, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error)
	ListObjectPermissions(ctx context.Context, profileID string) ([]models.ObjectPermissions, error)
	ListFieldPermissions(ctx context.Context, profileID string) ([]models.FieldPermissions, error)
	UpsertObjectPermission(ctx context.Context, perm models.ObjectPermissions) error
	// InsertMissingObjectPermissions inserts only absent object permissions (additive-only, never overwrites existing rows)
	InsertMissingObjectPermissions(ctx context.Context, perms []models.ObjectPermissions) error
	UpsertFieldPermission(ctx context.Context, perm models.FieldPermissions) error
	// InsertMissingFieldPermissions inserts only absent field permissions (additive-only, never overwrites existing rows)
	InsertMissingFieldPermissions(ctx context.Context, perms []models.FieldPermissions) error
	DeleteFieldPermissionsForFields(ctx context.Context, objectAPIName string, fieldAPINames []string) error
	// CountObjectPermissionsByProfile returns the count of object permissions for a profile
	CountObjectPermissionsByProfile(ctx context.Context, profileID string) (int, error)
	// BulkEnsureProfilePermissionSets creates or retrieves PermissionSets for multiple profiles in bulk
	BulkEnsureProfilePermissionSets(ctx context.Context, profiles []struct{ ID, Name string }) (map[string]string, error)
	// BulkCountObjectPermissionsByProfiles returns object permission counts for multiple profiles
	BulkCountObjectPermissionsByProfiles(ctx context.Context, profileIDs []string) (map[string]int, error)
	// BulkCountFieldPermissionsByProfiles returns field permission counts for multiple profiles
	BulkCountFieldPermissionsByProfiles(ctx context.Context, profileIDs []string) (map[string]int, error)
	// BulkCountObjectPermissionsByProfilesAndTypes returns object permission counts for multiple profiles
	// restricted to specific SObject API names.
	BulkCountObjectPermissionsByProfilesAndTypes(ctx context.Context, profileIDs []string, objectAPINames []string) (map[string]int, error)
	// BulkCountFieldPermissionsByProfilesAndTypes returns field permission counts for multiple profiles
	// restricted to specific SObject API names.
	BulkCountFieldPermissionsByProfilesAndTypes(ctx context.Context, profileIDs []string, objectAPINames []string) (map[string]int, error)
	ListPermissionSetObjectPermissions(ctx context.Context, permissionSetID string) ([]models.ObjectPermissions, error)
	ListPermissionSetFieldPermissions(ctx context.Context, permissionSetID string) ([]models.FieldPermissions, error)
	ListEffectiveObjectPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.ObjectPermissions, error)
	ListEffectiveFieldPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.FieldPermissions, error)
	// ListEffectiveFieldPermissionsForUserObject is the mandatory bounded
	// request-time projection. Administrative callers use the all-object method
	// above explicitly; request authorization must never fall back to it.
	ListEffectiveFieldPermissionsForUserObject(ctx context.Context, userID, profileID, objectAPIName string) ([]models.FieldPermissions, error)
	CreatePermissionSet(ctx context.Context, name, label, description string) (string, error)
	EnsureProfilePermissionSet(ctx context.Context, profileID, profileName string) (string, error)
	CleanupProfilePermissionSet(ctx context.Context, profileID string) error
	UpdatePermissionSet(ctx context.Context, id, name, label, description string) error
	DeletePermissionSet(ctx context.Context, id string) error
	GetUserProfileID(ctx context.Context, userID string) (string, error)
	GetAllRoles(ctx context.Context) ([]*models.UserRole, error)
	CreateRole(ctx context.Context, name, description string, parentRoleID *string) (string, error)
	GetRole(ctx context.Context, id string) (*models.UserRole, error)
	UpdateRole(ctx context.Context, id, name, description string, parentRoleID *string) error
	DeleteRole(ctx context.Context, id string) error
	IsUserInGroup(ctx context.Context, groupID, userID string) (bool, error)
	GetManualShareAccessLevels(ctx context.Context, objectAPIName, recordID, userID string) ([]string, error)
	GetTeamMemberAccessLevel(ctx context.Context, objectAPIName, recordID, userID string) (*string, error)
	GetRecordOwnerID(ctx context.Context, tableName, recordID string) (string, error)
	// GetRecordByID retrieves a full record as SObject for parent access checks
	// (e.g. ControlledByParent sharing needs full parent record for criteria-based sharing rules).
	GetRecordByID(ctx context.Context, tableName, recordID string) (models.SObject, error)
	GetPendingApprovalLockInfo(ctx context.Context, targetObjectID string) (processInstanceID string, lockType string, err error)
	HasPendingApprovalWorkitemForUser(ctx context.Context, processInstanceID, userID string) (bool, error)
	// SOQL RLS pre-resolution: resolve subqueries into ID lists
	GetUserGroupIDs(ctx context.Context, userID string) ([]string, error)
	GetManualShareRecordIDs(ctx context.Context, objectAPIName, userID string) ([]string, error)
	GetTeamMemberRecordIDs(ctx context.Context, objectAPIName, userID string) ([]string, error)
	// GetGroupMemberIDs is an existence probe (LIMIT 1) reporting whether the given
	// groups have any member — used so RLS can drop the group-from-owner sharing
	// arm (OwnerId IN (SELECT MemberId FROM closure …)) when none do (issue #2585).
	GetGroupMemberIDs(ctx context.Context, groupIDs []string) ([]string, error)
	GetAllProfiles(ctx context.Context) ([]*models.Profile, error)

	// System Permissions (Salesforce-compatible)
	ListSystemPermissions(ctx context.Context) ([]models.SystemPermission, error)
	GetEffectiveSystemPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error)
	GetEffectiveCustomPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error)
	GetEffectiveRecordTypeVisibilities(ctx context.Context, userID, profileID string) (map[string]models.PermissionSetRecordTypeVisibility, error)
	GetEffectiveAppVisibilities(ctx context.Context, userID, profileID string) (map[string]models.ProfileAppVisibility, error)
	GetEffectiveTabSettings(ctx context.Context, userID, profileID string) (map[string]models.ProfileTabSetting, error)
	EnsureNavigationVisibilityForProfile(ctx context.Context, profileID string) error
	SetSystemPermission(ctx context.Context, permSetID, apiName string, enabled bool) error
	GetPermissionSetGrants(ctx context.Context, permSetID string) (map[string]bool, error)
	GetPermissionSetGrantsMany(ctx context.Context, permSetIDs []string) (map[string]map[string]bool, error)
	LockPermissionSet(ctx context.Context, permSetID string) (bool, error)

	// Custom Permission CRUD
	ListCustomPermissions(ctx context.Context) ([]models.SystemCustomPermission, error)
	GetCustomPermission(ctx context.Context, id string) (*models.SystemCustomPermission, error)
	CreateCustomPermission(ctx context.Context, perm *models.SystemCustomPermission) error
	UpdateCustomPermission(ctx context.Context, perm *models.SystemCustomPermission) error
	DeleteCustomPermission(ctx context.Context, id string) error

	// Record Type Access Grants (PermissionSetRecordTypeVisibility)
	ListRecordTypeCatalog(ctx context.Context) ([]RecordTypeCatalogEntry, error)
	GetPermissionSetRecordTypeGrants(ctx context.Context, permSetID string) ([]models.PermissionSetRecordTypeVisibility, error)
	SetPermissionSetRecordTypeGrant(ctx context.Context, permSetID, recordTypeID string, isVisible bool) error

	// Custom Permission Grants (PermissionSetCustomPermission)
	GetPermissionSetCustomPermGrants(ctx context.Context, permSetID string) ([]models.PermissionSetCustomPermission, error)
	SetPermissionSetCustomPermGrant(ctx context.Context, permSetID, customPermID string) error
	DeletePermissionSetCustomPermGrant(ctx context.Context, permSetID, customPermID string) error

	// Permission Set Group CRUD
	ListPermissionSetGroups(ctx context.Context) ([]models.PermissionSetGroup, error)
	GetPermissionSetGroupByID(ctx context.Context, id string) (*models.PermissionSetGroup, error)
	GetPermissionSetGroupComponents(ctx context.Context, groupID string) ([]PermissionSetRef, error)
	CreatePermissionSetGroup(ctx context.Context, group *models.PermissionSetGroup, permSetIDs []string) error
	UpdatePermissionSetGroup(ctx context.Context, group *models.PermissionSetGroup, permSetIDs []string) error
	DeletePermissionSetGroup(ctx context.Context, id string) error
}

// UserWithPassword extends SystemUser to include password hash for auth checks
// Moved from persistence to allow interface decoupling
type UserWithPassword struct {
	*models.User
	PasswordHash string
}

// UserRepository defines the interface for user management
type UserRepository interface {
	CheckUserExistsByEmail(ctx context.Context, email string) (bool, error)
	CheckUserExistsByID(ctx context.Context, id string) (bool, error)
	CheckEmailConflict(ctx context.Context, email, excludeID string) (bool, error)
	UpdateUser(ctx context.Context, userID string, updates map[string]interface{}) error
	DeleteUser(ctx context.Context, userID string) error
	FindAll(ctx context.Context) ([]*models.User, error)
	GetUserRoleID(ctx context.Context, userID string) (*string, error)
	FindUserByEmailWithPassword(ctx context.Context, email string) (*UserWithPassword, error)
	FindUserByIDWithPassword(ctx context.Context, userID string) (*UserWithPassword, error)
	UpdatePassword(ctx context.Context, userID, passwordHash string) error
	GetUserByID(ctx context.Context, userID string) (*models.User, error)
	CreateUser(ctx context.Context, user *models.User) error
	GetFirstUserIDByProfile(ctx context.Context, profileID string) (string, error)
	GetRoleNameByID(ctx context.Context, roleID string) (string, error)
	GetProfileNameByID(ctx context.Context, profileID string) (string, error)
	GetUserIDsByRoleIDs(ctx context.Context, roleIDs []string) ([]string, error)

	// ResetFailedLoginAttempts resets the failed login counter and clears any lockout for a user.
	ResetFailedLoginAttempts(ctx context.Context, userID string) error

	// IncrementFailedAndLockIfNeeded atomically increments the failed login counter
	// and locks the account if the threshold is reached.
	IncrementFailedAndLockIfNeeded(ctx context.Context, userID string, maxAttempts int, lockDuration time.Duration) (locked bool, err error)
}

// BulkBatchAdmissionOutcome says whether a batch registration became durable.
// The distinction between not-created and unknown is what lets an object-store
// caller compensate only after the SQL transaction is proven not to have
// committed.
type BulkBatchAdmissionOutcome string

const (
	BulkBatchAdmissionCreated    BulkBatchAdmissionOutcome = "created"
	BulkBatchAdmissionNotCreated BulkBatchAdmissionOutcome = "not_created"
	BulkBatchAdmissionUnknown    BulkBatchAdmissionOutcome = "unknown"
)

// BulkRepository defines the interface for bulk job management
type BulkRepository interface {
	CreateJob(ctx context.Context, job *models.BulkJob) error
	GetJob(ctx context.Context, jobId string) (*models.BulkJob, error)
	UpdateJob(ctx context.Context, job *models.BulkJob) error
	CreateBatchIfJobOpen(ctx context.Context, batch *models.BulkBatch) (BulkBatchAdmissionOutcome, error)
	AcceptIngestJob(ctx context.Context, jobID string) (bool, error)
	AbortIngestJob(ctx context.Context, jobID string) (bool, error)
	VerifyActiveWorker(ctx context.Context, workFamily string) error
	GetBatches(ctx context.Context, jobId string) ([]*models.BulkBatch, error)
}

// SearchOptions tunes how QueryRepository.Search matches and which table it reads.
// The zero value preserves the legacy full-search behavior: case-folded substring
// match (LOWER(col) LIKE '%term%') against the formula-view-resolved read target.
//
// Prefix mode (used by the /search/suggestions name-prefix endpoint) emits an
// index-eligible anchored match (col LIKE 'term%', no LOWER wrapper, no leading
// wildcard) against physical columns only. ForceBaseTable bypasses the formula
// view resolver and reads the object's base table, because a prefix LIKE against
// the v_<Object> formula view could never use the base-table name index.
type SearchOptions struct {
	Prefix         bool
	ForceBaseTable bool
}

// QueryCorrelatedMembershipPlan is a service-validated runtime plan for a
// neutral correlated-membership predicate. It intentionally lives at the
// repository port boundary, not in shared request models: QueryService owns
// metadata/FLS/RLS validation, and persistence consumes the prepared row-read
// inputs without making them a wire/request fact.
type QueryCorrelatedMembershipPlan struct {
	ID               string
	OuterField       string
	Negate           bool
	InnerSchema      *models.ObjectMetadata
	InnerSelectField string
	InnerRequest     models.QueryRequest
	InnerRLSResult   *rls.Result
}

// CorrelatedMembershipQueryRepository is the optional row-query executor for
// QuerySpec correlated-membership predicates. QueryRepository keeps the
// historical Find contract for ordinary reads while implementations that own
// the row SQL lowering expose this narrower extension.
type CorrelatedMembershipQueryRepository interface {
	FindWithCorrelatedMemberships(ctx context.Context, tableSchema *models.ObjectMetadata, req models.QueryRequest, rlsResult *rls.Result, visibleFields []string, memberships []QueryCorrelatedMembershipPlan) (*models.QueryResult, error)
}

// CorrelatedMembershipAnalyticsRepository is the optional aggregate-query
// executor for service-validated correlated-membership predicates.
type CorrelatedMembershipAnalyticsRepository interface {
	RunAnalyticsWithCorrelatedMemberships(ctx context.Context, tableSchema *models.ObjectMetadata, q models.AnalyticsQuery, rlsResult *rls.Result, memberships []QueryCorrelatedMembershipPlan) (interface{}, error)
}

// QueryRepository defines the interface for complex read operations
type QueryRepository interface {
	Find(ctx context.Context, tableSchema *models.ObjectMetadata, req models.QueryRequest, rlsResult *rls.Result, visibleFields []string) (*models.QueryResult, error)
	Search(ctx context.Context, schema *models.ObjectMetadata, term string, searchFields []string, selectFields []string, limit int, offset int, orderBy string, orderDir string, filterExpr string, rlsResult *rls.Result, opts SearchOptions) ([]models.SObject, error)
	// RunAnalytics executes an aggregation query
	RunAnalytics(ctx context.Context, tableSchema *models.ObjectMetadata, q models.AnalyticsQuery, rlsResult *rls.Result) (interface{}, error)
	ExecuteRawSQL(ctx context.Context, sql string, params []interface{}) ([]models.SObject, error)
	GetLookupNames(ctx context.Context, tableSchema *models.ObjectMetadata, ids []string, idField string, nameField string) ([]models.SObject, error)
	GetRecordsByIDs(ctx context.Context, tableSchema *models.ObjectMetadata, ids []string, fields []string) ([]models.SObject, error)
}

// OAuthRepository defines the interface for OAuth code persistence
type OAuthRepository interface {
	// StoreAuthCode saves an authorization code with expiration
	StoreAuthCode(ctx context.Context, code, clientID, redirectURI, scope, userID, nonce string, expiresAt time.Time) error
	StoreAuthCodeRequest(ctx context.Context, req OAuthCodeCreateRequest) error
	StoreAuthorizeRequest(ctx context.Context, req OAuthAuthorizeRequestCreateRequest) error

	// GetAuthCode retrieves a usable code without consuming it.
	// Returns nil if code not found, expired, or already used.
	GetAuthCode(ctx context.Context, code string) (*OAuthCodeData, error)

	// ConsumeAuthCode retrieves and marks a code as used (one-time use)
	// Returns nil if code not found, expired, or already used
	ConsumeAuthCode(ctx context.Context, code string) (*OAuthCodeData, error)
	GetAuthorizeRequest(ctx context.Context, handle string) (*OAuthAuthorizeRequestData, error)
	ConsumeAuthorizeRequest(ctx context.Context, handle string) (*OAuthAuthorizeRequestData, error)
	ConsumeAuthorizeRequestAndStoreAuthCode(ctx context.Context, handle string, req OAuthCodeCreateRequest) (*OAuthAuthorizeRequestData, error)

	// CleanupExpiredCodes removes expired codes (for background cleanup)
	CleanupExpiredCodes(ctx context.Context) (int64, error)

	// StoreRefreshToken persists a hashed, rotating OAuth refresh token. Only
	// the hash is stored; the plaintext is returned to the client once.
	StoreRefreshToken(ctx context.Context, token *models.SystemOAuthRefreshToken) error
	// GetRefreshTokenByHash returns the refresh token row for a hash, or
	// (nil, nil) if none exists. Revocation/expiry are not filtered here.
	GetRefreshTokenByHash(ctx context.Context, tokenHash string) (*models.SystemOAuthRefreshToken, error)
	// RevokeRefreshToken revokes one refresh token by id; returns true when a
	// still-active row was revoked (used to rotate on each refresh).
	RevokeRefreshToken(ctx context.Context, id string) (bool, error)
	// RevokeRefreshTokenFamily revokes every still-active refresh token sharing a
	// family id (replay defense); returns the number of rows revoked. It
	// serializes per family so a concurrent rotation cannot slip an active
	// successor past the sweep.
	RevokeRefreshTokenFamily(ctx context.Context, familyID string) (int, error)
	// RevokeRefreshTokensForUser revokes every still-active refresh token owned by
	// a user; returns the number revoked. Used to fold refresh tokens into
	// credential revocation (password change / reset) so a stolen refresh token
	// cannot outlive the session revocation.
	RevokeRefreshTokensForUser(ctx context.Context, userID string) (int, error)
	// RevokeRefreshTokensForConnectedApp revokes every still-active refresh token
	// issued for a Connected App; returns the number revoked. It honors a
	// transaction carried on the context so it can run atomically with the
	// session revocation triggered by a Connected App security-policy change.
	RevokeRefreshTokensForConnectedApp(ctx context.Context, appID string) (int, error)
	// RevokeRefreshTokensForUserAndConnectedApp revokes only one subject's
	// refresh-token families for one authorization client. It must honor a
	// context transaction so disconnect is one atomic credential cutover.
	RevokeRefreshTokensForUserAndConnectedApp(ctx context.Context, userID, appID string) (int, error)
	// RotateRefreshToken atomically consumes the presented refresh token (by
	// hash) and inserts its successor within a single transaction, serialized per
	// family. This is the authoritative single-use + family-replay gate: it must
	// be impossible for a concurrent replay to revoke the family between the
	// presented token's revocation and the successor's insertion.
	//   - rotated=true: the presented token was active; it is now revoked and the
	//     successor is stored.
	//   - reuseDetected=true: the presented token was already revoked or lost the
	//     single-use CAS (a replay/duplicate use); the whole family is revoked and
	//     no successor is stored.
	//   - rotated=false && reuseDetected=false: the presented token no longer
	//     exists; nothing was changed.
	RotateRefreshToken(ctx context.Context, presentedHash string, successor *models.SystemOAuthRefreshToken) (rotated bool, reuseDetected bool, err error)
}

// OAuthCodeCreateRequest captures the authorization transaction that is bound
// to a one-time OAuth authorization code.
type OAuthCodeCreateRequest struct {
	Code                string
	CodeHash            string
	ClientID            string
	RedirectURI         string
	Scope               string
	UserID              string
	Nonce               string
	ExpiresAt           time.Time
	CodeChallenge       string
	CodeChallengeMethod string
	AuthTime            time.Time
	Issuer              string
	ConsentID           string
	ClientTypeAtIssue   string
	Resource            string
}

// OAuthCodeData represents an OAuth authorization code record
type OAuthCodeData struct {
	ClientID            string
	RedirectURI         string
	Scope               string
	UserID              string
	Nonce               string
	ExpiresAt           time.Time
	CodeChallenge       string
	CodeChallengeMethod string
	AuthTime            time.Time
	Issuer              string
	ConsentID           string
	ClientTypeAtIssue   string
	Resource            string
}

// OAuthAuthorizeRequestCreateRequest captures a validated pre-login authorize
// request snapshot that can be resumed by a platform browser session.
type OAuthAuthorizeRequestCreateRequest struct {
	Handle              string
	HandleHash          string
	ClientID            string
	RedirectURI         string
	Scope               string
	State               string
	CodeChallenge       string
	CodeChallengeMethod string
	Issuer              string
	Resource            string
	ExpiresAt           time.Time
}

// OAuthAuthorizeRequestData represents a stored pre-login authorize request
// snapshot consumed by the login/resume bridge.
type OAuthAuthorizeRequestData struct {
	ClientID            string
	RedirectURI         string
	Scope               string
	State               string
	CodeChallenge       string
	CodeChallengeMethod string
	Issuer              string
	Resource            string
	ExpiresAt           time.Time
}
