package contract

import (
	"context"
	"io"
	"time"

	"github.com/nexuscrm/shared/pkg/constants"
	sharedErrors "github.com/nexuscrm/shared/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

// Records is the neutral data-plane surface available to API dialects. It
// deliberately mirrors only the operations that are implemented below the
// dialect boundary today; unresolved design surfaces stay out until their
// concrete service contracts exist.
type Records interface {
	Insert(ctx context.Context, objectName string, data Record, user *models.UserSession) (Record, error)
	Retrieve(ctx context.Context, objectName string, id string, user *models.UserSession) (Record, error)
	RetrieveMany(ctx context.Context, objectName string, ids []string, user *models.UserSession) (map[string]Record, error)
	Update(ctx context.Context, objectName string, id string, updates Record, user *models.UserSession) error
	Delete(ctx context.Context, objectName string, id string, user *models.UserSession) error
	UpsertByExternalID(ctx context.Context, objectName string, fieldName string, fieldValue string, data Record, user *models.UserSession) (Record, bool, error)
	RetrieveByField(ctx context.Context, objectName string, fieldName string, fieldValue string, user *models.UserSession) (Record, error)

	CreateRecords(ctx context.Context, records []Record, allOrNone bool, user *models.UserSession) ([]RecordMutationResult, error)
	UpdateRecords(ctx context.Context, records []Record, allOrNone bool, user *models.UserSession) ([]RecordMutationResult, error)
	DeleteRecords(ctx context.Context, ids []string, allOrNone bool, user *models.UserSession) ([]RecordMutationResult, error)
	UpsertRecords(ctx context.Context, objectName string, externalIDField string, records []Record, allOrNone bool, user *models.UserSession) ([]RecordMutationResult, error)
	RetrieveRecords(ctx context.Context, objectName string, ids []string, fields []string, user *models.UserSession) ([]Record, error)
	ObjectTypeForID(ctx context.Context, id string) (string, error)
}

// TreeNode is one node in the neutral nested tree-create IR. Every fact a node
// needs — its object type, its intra-batch alias, its user-authored field
// values, and its child relationships — is a structured field on this type. A
// dialect that speaks a wire tree grammar (such as the SF Composite Tree, which
// muxes the object type and intra-batch alias into a per-node metadata sub-map
// and wraps each relationship's child collection inside a named sub-object) does
// that decode itself and hands the engine this fully structured node; nothing
// about the wire grammar crosses this boundary.
type TreeNode struct {
	// ObjectType is the neutral object API name for this node's record. Root
	// nodes self-describe their object type here exactly as nested nodes do, so
	// the engine needs no separate root-type argument.
	ObjectType string
	// ReferenceID is the intra-batch alias other nodes may target before this
	// node has a DB id. It may be "" when no other node references this node.
	ReferenceID string
	// Fields carries the record's user-authored field values only. It excludes
	// the object type, the intra-batch alias, and child relationships, which are
	// the structured fields above rather than in-band map entries.
	Fields Record
	// Children maps a neutral relationship name to the child nodes reachable
	// through it. The engine resolves each relationship to a foreign-key field
	// and stamps the parent's freshly created id onto every child.
	Children map[string][]TreeNode
}

// TreeCreateResult is the neutral outcome of creating one ROOT node of a tree.
// CreateTreeNodes returns one result per ROOT node — descendant nodes are
// created transactionally but do not each yield an entry here. On
// success Errors is empty, ID holds the root record's freshly created id, and
// ReferenceID echoes that root node's intra-batch alias so callers can map the
// root alias to its new id.
type TreeCreateResult struct {
	ReferenceID string
	ID          string
	Errors      []string
}

// TreeNodeWriter is the neutral tree-create capability, kept as a SEPARATE
// capability interface from Records rather than a method on Records. Only the
// records engine (nexuscrm-records ContractService) implements it; a caller that
// holds a Records value type-asserts to TreeNodeWriter to reach it. Keeping it
// off Records means adding this capability does not force every Records
// implementer (including dialect test fakes) to grow a method. It is the neutral
// tree-create surface: a dialect that speaks a wire tree grammar decodes that
// grammar into TreeNode itself and reaches this seam via the type assertion.
type TreeNodeWriter interface {
	CreateTreeNodes(ctx context.Context, nodes []TreeNode, user *models.UserSession) ([]TreeCreateResult, error)
}

// RecordAccessReader is the neutral read-side capability for callers that need
// record data and the caller-effective record-level access decision through the
// same authoritative records engine path. Dialects own any public field names
// such as hasReadAccess; the contract carries only generic access facts.
type RecordAccessReader interface {
	RetrieveWithAccess(ctx context.Context, objectName string, id string, user *models.UserSession) (Record, *RecordAccess, error)
	BatchRecordAccess(ctx context.Context, objectName string, ids []string, user *models.UserSession) (map[string]RecordAccess, error)
	ObjectTypesForIDs(ctx context.Context, ids []string) (map[string]string, error)
}

type RecordAccess struct {
	Read   bool
	Update bool
	Delete bool
	Locked bool
}

// RecordDefaultPreviewer exposes metadata-driven default preview helpers that
// are implemented below the records engine. API dialects decide how defaulted
// values are represented on their wire.
type RecordDefaultPreviewer interface {
	ValidateSiblingMaxDefaultConfig(ctx context.Context, objectName string, targetField string) error
	ValidateSiblingMaxDefaultParentForCreate(ctx context.Context, objectName string, parentField string, parentValue any, user *models.UserSession) error
	PreviewSiblingMaxDefault(ctx context.Context, objectName string, targetField string, parentField string, parentValue any) (int64, error)
}

// RecordMutationCoordinator exposes transaction-boundary facts needed by
// dialect-owned multi-step record mutations without giving dialects a DB handle.
type RecordMutationCoordinator interface {
	RunInPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error
	RequiresSourceAwareTransaction(objectName string) bool
}

type RecordMutationErrorKind string

const (
	RecordMutationErrorProcessingHalted      RecordMutationErrorKind = "processing_halted"
	RecordMutationErrorAllOrNoneRolledBack   RecordMutationErrorKind = "all_or_none_operation_rolled_back"
	RecordMutationErrorInternal              RecordMutationErrorKind = "internal"
	RecordMutationErrorInvalidFieldForUpdate RecordMutationErrorKind = "invalid_field_for_insert_update"
	RecordMutationErrorCustomValidation      RecordMutationErrorKind = "field_custom_validation_exception"
	RecordMutationErrorInvalidCrossReference RecordMutationErrorKind = "invalid_cross_reference_key"
	RecordMutationErrorFieldIntegrity        RecordMutationErrorKind = "field_integrity_exception"
	RecordMutationErrorInvalidOperation      RecordMutationErrorKind = "invalid_operation"
	RecordMutationErrorInvalidField          RecordMutationErrorKind = "invalid_field"
	RecordMutationErrorNotFound              RecordMutationErrorKind = "not_found"
	RecordMutationErrorMalformedID           RecordMutationErrorKind = "malformed_id"
	RecordMutationErrorInvalidID             RecordMutationErrorKind = "invalid_id"
)

type RecordMutationError struct {
	Kind                  RecordMutationErrorKind
	Message               string
	Fields                []string
	CreateFromSourceGuard *sharedErrors.CreateFromSourceGuardDetails
}

type RecordMutationResult struct {
	ID      string
	Success bool
	Created *bool
	Errors  []RecordMutationError
}

type Record map[string]any

// RecycleBin is the neutral deleted-record lifecycle surface. The records
// engine owns tombstone lookup, visibility, restore, and purge semantics; API
// dialects own URL shape and response/error envelopes.
type RecycleBin interface {
	ListRecycleBin(ctx context.Context, user *models.UserSession, scope RecycleBinScope) (*RecycleBinListResult, error)
	RestoreRecycleBinRecord(ctx context.Context, recordID string, user *models.UserSession) error
	PurgeRecycleBinRecord(ctx context.Context, recordID string, user *models.UserSession) error
}

type RecycleBinScope string

const (
	RecycleBinScopeMine RecycleBinScope = "mine"
	RecycleBinScopeAll  RecycleBinScope = "all"
)

type RecycleBinItem struct {
	ID               string
	RecordID         string
	RecordName       string
	ObjectAPIName    string
	DeletedBy        string
	DeletedDate      string
	CreatedDate      string
	LastModifiedDate string
}

type RecycleBinFailureCode string

const (
	RecycleBinFailureCodeUnknown   RecycleBinFailureCode = "unknown"
	RecycleBinFailureCodeForbidden RecycleBinFailureCode = "forbidden"
	RecycleBinFailureCodeInvalid   RecycleBinFailureCode = "invalid_request"
	RecycleBinFailureCodeInternal  RecycleBinFailureCode = "internal"
)

type RecycleBinFailure struct {
	ObjectAPIName string
	ErrorCode     string
	Message       string
}

type RecycleBinListResult struct {
	Items         []RecycleBinItem
	FailedObjects []RecycleBinFailure
}

// AuditLog is the neutral privileged audit-log read surface. The platform owns
// _System_AuditLog storage, filtering, and EXPORT_AUDIT meta-audit emission;
// dialects own URL/query binding and wire projection.
type AuditLog interface {
	QueryAuditLog(ctx context.Context, req AuditLogQueryRequest, user *models.UserSession) (*AuditLogQueryResult, error)
}

type AuditLogQueryRequest struct {
	ObjectAPIName string
	// RecordID narrows to one record when non-empty. Zero value means no
	// record filter (an object-wide read on this privileged surface).
	RecordID string
	Limit    int
	Offset   int
	// Optional narrowing filters; zero values mean "no filter".
	// ChangedByID narrows to rows written by one acting principal. FieldName
	// matches the FieldName column exactly (for example the record-head
	// sentinel rows). Since bounds ChangedAt from below (inclusive).
	ChangedByID string
	FieldName   string
	Since       time.Time
	// Outcome narrows to rows with exactly this Outcome value.
	//
	// Callers that count or reason about things that ACTUALLY HAPPENED must
	// set this to SUCCESS. Since #4261 the table also carries FAILURE head
	// rows recording saves the validation pipeline REFUSED — a refused save
	// changed nothing, so counting one as a real change overstates activity.
	// The zero value means "no filter", which returns both.
	Outcome string
}

type AuditLogQueryResult struct {
	Rows   []AuditLogRow
	Limit  int
	Offset int
	Object string
}

type AuditLogRow struct {
	ID               string
	ObjectAPIName    string
	RecordID         string
	FieldName        string
	OldValue         *string
	NewValue         *string
	ChangedByID      string
	ChangedAt        time.Time
	ChangedAtValid   bool
	Action           *string
	ChangesJSON      *string
	Entrypoint       *string
	EntrypointDetail *string
	RequestID        *string
	TraceID          *string
	SessionID        *string
	ClientIP         *string
	ActualUserID     *string
	Outcome          *string
	ErrorCode        *string
}

type AuditLogErrorKind string

const (
	AuditLogErrorUnavailable AuditLogErrorKind = "unavailable"
	AuditLogErrorQueryFailed AuditLogErrorKind = "query_failed"
	AuditLogErrorScanFailed  AuditLogErrorKind = "scan_failed"
	AuditLogErrorIterate     AuditLogErrorKind = "iterate_failed"
)

type AuditLogError struct {
	Kind    AuditLogErrorKind
	Message string
	Cause   error
}

func (e *AuditLogError) Error() string {
	if e == nil {
		return ""
	}
	if e.Message != "" {
		return e.Message
	}
	return string(e.Kind)
}

func (e *AuditLogError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Cause
}

// RecordAttachmentSourceLookup is implemented by app/dialect code that knows how
// to map its own source reference vocabulary to a local platform file id. It
// performs no authorization by itself; callers must invoke it only through
// Files.ResolveAuthorizedRecordAttachmentSource.
type RecordAttachmentSourceLookup interface {
	LookupRecordAttachmentSource(ctx context.Context, req RecordAttachmentSourceRequest) (*RecordAttachmentSourceLookupResult, bool, error)
}

type RecordAttachmentSourceLookupResult struct {
	FileID string
}

type RecordAttachmentSourceRequest struct {
	ParentObjectAPIName string
	ParentID            string
	SourceObject        string
	SourceField         string
	SourceValue         string
}

type RecordAttachmentSourceResult struct {
	FileID   string
	FileName string
}

// RecordAttachmentMetadataAuthorizer verifies that the caller can read a local
// record attachment and returns metadata that is already safe to disclose.
type RecordAttachmentMetadataAuthorizer interface {
	AuthorizeRecordAttachmentMetadata(ctx context.Context, fileID string, user *models.UserSession) (*RecordAttachmentMetadata, bool, error)
}

type RecordAttachmentMetadata struct {
	FileID        string
	FileName      string
	ContentType   string
	ContentLength uint64
	ContentDigest string
}

// Files is the neutral file/blob surface available to API dialects. It owns the
// platform mechanics below the dialect boundary: object storage, platform file
// metadata writes, parent-record authorization, and detached blob storage.
// Dialects decide whether those primitives appear as attachments, issue assets,
// or another product-specific wire surface.
type Files interface {
	StoreDetachedFile(ctx context.Context, req FileUploadRequest) (*FileUploadResult, error)
	StoreRecordAttachment(ctx context.Context, req FileUploadRequest) (*FileUploadResult, error)
	OpenDetachedFile(ctx context.Context, ref FileBlobReference, user *models.UserSession) (*FileDownload, error)
	OpenRecordAttachment(ctx context.Context, fileID string, user *models.UserSession) (*FileDownload, bool, error)
	AuthorizeRecordAttachmentMetadata(ctx context.Context, fileID string, user *models.UserSession) (*RecordAttachmentMetadata, bool, error)
	ResolveAuthorizedRecordAttachmentSource(ctx context.Context, req RecordAttachmentSourceRequest, lookup RecordAttachmentSourceLookup, user *models.UserSession) (*RecordAttachmentSourceResult, bool, error)
	DeleteRecordAttachment(ctx context.Context, fileID string, user *models.UserSession) error
}

type FileUploadRequest struct {
	ID                  string
	StorageKey          string
	LegacyFileName      string
	FileName            string
	Extension           string
	ContentType         string
	Body                io.Reader
	ContentLength       int64
	ParentID            string
	ParentObjectAPIName string
	AttachmentKind      string
	User                *models.UserSession
}

type FileBlobReference struct {
	StorageKey     string
	LegacyFileName string
	FileName       string
}

type FileUploadResult struct {
	ID   string
	Name string
	Size int64
	Mime string
}

type FileDownload struct {
	Reader      io.ReadCloser
	FileName    string
	Size        int64
	ContentType string
}

// Activity is the neutral recent-activity surface used by dialects for record
// access tracking and relevant-items reads. It is deliberately smaller than the
// MRU service: dialects do not own pinning, object-order mutation, or repository
// details.
type Activity interface {
	TrackRecordAccess(ctx context.Context, userID string, objectAPIName string, recordID string)
	RelevantItems(ctx context.Context, userID string, objectAPINames []string, lastUpdatedID string) ([]RelevantItem, string, error)
}

type RelevantItem struct {
	ObjectAPIName string
	RecordID      string
	Name          string
}

type SearchResult struct {
	ObjectLabel   string
	ObjectAPIName string
	Icon          string
	Matches       []Record
}

// SearchScopeItem is one neutral global-search scope entry. It carries object
// identity and display metadata only; dialects own URLs and wire field names.
type SearchScopeItem struct {
	ObjectAPIName string
	Name          string
	Label         string
	KeyPrefix     string
}

// SearchScope returns the user-visible global-search scope order after the host
// has merged user preference, installed-pack defaults, metadata searchability,
// and read permissions. That merging is a platform capability; dialects should
// only project the resulting neutral items to their wire shape.
type SearchScope interface {
	ScopeOrder(ctx context.Context, user *models.UserSession) []SearchScopeItem
	// CuratedObjectNames returns the installed-pack-authored dropdown order after
	// read-permission filtering, without MRU merging or metadata fallback
	// appends. It backs Nexus' metadata curation surface, not Salesforce's
	// /search/scopeOrder wire DTO.
	CuratedObjectNames(ctx context.Context, user *models.UserSession) []string
}

type QueryResult struct {
	TotalSize        int
	TotalSizeUnknown bool
	Done             bool
	Records          []Record
	IsAggregate      bool
}

// Reports is the neutral report execution surface used by product dialects that
// render their own report or dashboard wire shape. The report metadata and
// result models are substrate data, not transport envelopes.
type Reports interface {
	GetReportMetadata(ctx context.Context, reportID string, user *models.UserSession) (*models.ReportDefinition, error)
	RunReportDefinition(ctx context.Context, def *models.ReportDefinition, user *models.UserSession, runtimeFilters map[string]interface{}, limit int) (*models.ReportResult, error)
}

// VirtualReportSource executes one app-registered, non-physical report source.
// The report engine validates the registration and request, then applies its own
// RLS/FLS checks to every returned row before exposing Values to a caller.
type VirtualReportSource interface {
	Execute(ctx context.Context, request VirtualReportSourceRequest, user *models.UserSession) (*VirtualReportSourceResult, error)
}

// VirtualReportSourceRegistration is the immutable boot-time description of one
// virtual report source. SourceRef is the only dispatch key; product-specific
// source names and column shapes remain registered L1 content.
type VirtualReportSourceRegistration struct {
	SourceRef                string
	ParentObjectAPIName      string
	RowFieldSecurityRequired bool
	Capabilities             VirtualReportSourceCapabilities
	Columns                  []VirtualReportColumn
	Joins                    []VirtualReportJoin
	Source                   VirtualReportSource
}

// VirtualReportSourceCapabilities makes unsupported execution shapes explicit.
// Report execution fails closed when a definition requests an undeclared
// capability; it never drops filters, grouping, joins, or derived columns.
type VirtualReportSourceCapabilities struct {
	Filters    bool
	Grouping   bool
	Joins      bool
	Derived    bool
	Pagination bool
}

// VirtualReportColumn declares one result column and the underlying fields whose
// read permission is required. MetadataOnly is reserved for values such as a
// record identifier that has no field-level dependency. JoinRef names a
// declared join. Derived columns identify a platform-query projection (for
// example a metadata formula field) in FieldReferences; product code cannot
// return an independently computed payload.
type VirtualReportColumn struct {
	Name            string
	MetadataOnly    bool
	JoinRef         string
	Derived         bool
	FieldReferences []VirtualReportFieldReference
	Deriver         VirtualReportValueDeriver
}

// VirtualReportValueDeriver is product-owned registered content. The report
// engine invokes it only with authoritative values that it has reloaded and
// authorized from the evidence returned for one row. Implementations must be
// stateless and safe for concurrent use.
type VirtualReportValueDeriver interface {
	DeriveVirtualReportValue([]interface{}) (interface{}, error)
}

// VirtualReportFieldReference is an object/field FLS dependency.
type VirtualReportFieldReference struct {
	ObjectAPIName string
	FieldAPIName  string
}

// VirtualReportJoin describes a related object used by registered source
// columns. Each returned row must carry an access record for every requested
// join in addition to the mandatory parent record. SourceRecordJoinRef is empty
// for the parent record or names a required supporting join; its
// SourceFieldAPIName value is the authoritative related-record ID. Optional
// permits omission only when that reloaded field is empty.
type VirtualReportJoin struct {
	Name                string
	ObjectAPIName       string
	SourceRecordJoinRef string
	SourceFieldAPIName  string
	Optional            bool
}

// VirtualReportSourceRequest carries the normalized report plan to the source.
// Columns and Joins are the exact registered descriptors referenced by the
// definition, filters, grouping, aggregates, date filter, and runtime filters.
type VirtualReportSourceRequest struct {
	Definition     models.ReportDefinition
	RuntimeFilters map[string]interface{}
	Columns        []VirtualReportColumn
	Joins          []VirtualReportJoin
	Limit          int
	Offset         int
}

type VirtualReportSourceResult struct {
	// TotalCount is the unpaginated count after the source applied all filters
	// and its parent-record visibility constraint. Zero means not computed.
	TotalCount int
	Rows       []VirtualReportRow
}

// VirtualReportRow binds every report-visible value to its field-authorization
// evidence. AccessRecords are never copied into the report result. The parent
// object's record is mandatory.
type VirtualReportRow struct {
	Values        map[string]VirtualReportValue
	AccessRecords []VirtualReportAccessRecord
	// HistoryRecordID is the row's authoritative history anchor. Non-derived
	// history values must project this record; derived columns may reference
	// additional same-parent history evidence that the report engine reloads.
	HistoryRecordID string
}

// VirtualReportValue identifies a field on one of the row's authoritative
// records. The report engine reloads that record through the platform query
// path, enforces the column's declared FLS dependencies, and projects the
// report-visible value. RowFieldReference binds a value to the selected parent
// field when row-dependent FLS is required.
type VirtualReportValue struct {
	AccessObjectAPIName string
	RecordFieldName     string
	RowFieldReference   *VirtualReportFieldReference
	History             *VirtualReportHistoryProjection
	HistoryInputs       []VirtualReportHistoryProjection
}

// RecordObjectName returns the neutral object name whose record authorizes
// this value. Keep the legacy exported field as the app-facing compatibility
// seam; new L0 consumers should use this accessor so dialect vocabulary does
// not spread beyond the contract owner.
func (v VirtualReportValue) RecordObjectName() string {
	return v.AccessObjectAPIName
}

// VirtualReportHistoryProjection identifies one value on authoritative history
// evidence. The report engine reloads every record through QueryHistory before
// projection.
type VirtualReportHistoryProjection struct {
	RecordID string
	Field    VirtualReportHistoryField
}

type VirtualReportHistoryField string

const (
	VirtualReportHistoryID          VirtualReportHistoryField = "Id"
	VirtualReportHistoryParentID    VirtualReportHistoryField = "ParentId"
	VirtualReportHistoryFieldName   VirtualReportHistoryField = "Field"
	VirtualReportHistoryOldValue    VirtualReportHistoryField = "OldValue"
	VirtualReportHistoryNewValue    VirtualReportHistoryField = "NewValue"
	VirtualReportHistoryDataType    VirtualReportHistoryField = "DataType"
	VirtualReportHistoryCreatedDate VirtualReportHistoryField = "CreatedDate"
	VirtualReportHistoryCreatedByID VirtualReportHistoryField = "CreatedById"
)

type VirtualReportAccessRecord struct {
	ObjectAPIName string
	RecordID      string
}

type FolderAccessLevel string

const (
	FolderAccessView   FolderAccessLevel = "View"
	FolderAccessManage FolderAccessLevel = "Manage"
)

// FolderAccess checks folder-level authorization below dialect-specific folder
// and dashboard wire surfaces.
type FolderAccess interface {
	CheckFolderAccess(ctx context.Context, user *models.UserSession, folderID string, required FolderAccessLevel) bool
}

// ResourceAccessListRequest identifies one resource whose ACL grants should be
// listed. The resource type is host-defined; dialects own any public wire shape.
type ResourceAccessListRequest struct {
	ResourceType string
	ResourceID   string
}

// ResourceAccessGetRequest identifies one ACL grant by its host-defined grant
// ID. The returned grant still carries its resource ID so callers can enforce
// parent-route consistency.
type ResourceAccessGetRequest struct {
	ResourceType string
	GrantID      string
}

// ResourceAccessGrant is one generic resource ACL grant. It deliberately uses
// resource/principal terms rather than app-specific object or share names.
type ResourceAccessGrant struct {
	ID            string
	ResourceType  string
	ResourceID    string
	PrincipalID   string
	PrincipalType string
	AccessLevel   string
}

// ResourceAccessGrantInput is the caller-supplied shape of one generic ACL grant
// mutation. The host owns concrete storage and grant IDs; dialects own public
// wire fields such as "sharedToId".
type ResourceAccessGrantInput struct {
	PrincipalID   string
	PrincipalType string
	AccessLevel   string
}

type ResourceVisibilityRequest struct {
	ResourceType string
}

type ResourceAccessCreateRequest struct {
	ResourceType string
	ResourceID   string
	CreatedByID  string
	Grant        ResourceAccessGrantInput
}

type ResourceAccessEnsureRequest struct {
	ResourceType string
	ResourceID   string
	CreatedByID  string
	Grant        ResourceAccessGrantInput
}

type ResourceAccessReplaceRequest struct {
	ResourceType string
	ResourceID   string
	CreatedByID  string
	Grants       []ResourceAccessGrantInput
}

type ResourceAccessUpdateRequest struct {
	ResourceType string
	GrantID      string
	AccessLevel  string
}

type ResourceAccessDeleteRequest struct {
	ResourceType string
	GrantID      string
}

type ResourceAccessDeleteByPrincipalTypeRequest struct {
	ResourceType  string
	ResourceID    string
	PrincipalType string
}

type ResourceOwnershipTransferRequest struct {
	ResourceType  string
	SourceOwnerID string
	TargetOwnerID string
}

// ResourceAccess owns generic ACL grants and ownership facts for
// resource-shaped substrate that is not modeled as ordinary records CRUD.
type ResourceAccess interface {
	ListVisibleResourceIDs(ctx context.Context, user *models.UserSession, req ResourceVisibilityRequest) ([]string, error)
	ListResourceAccessGrants(ctx context.Context, user *models.UserSession, req ResourceAccessListRequest) ([]ResourceAccessGrant, error)
	GetResourceAccessGrant(ctx context.Context, user *models.UserSession, req ResourceAccessGetRequest) (*ResourceAccessGrant, error)
	CreateResourceAccessGrant(ctx context.Context, user *models.UserSession, req ResourceAccessCreateRequest) (*ResourceAccessGrant, error)
	EnsureResourceAccessGrant(ctx context.Context, user *models.UserSession, req ResourceAccessEnsureRequest) (*ResourceAccessGrant, error)
	ReplaceResourceAccessGrants(ctx context.Context, user *models.UserSession, req ResourceAccessReplaceRequest) ([]ResourceAccessGrant, error)
	UpdateResourceAccessGrant(ctx context.Context, user *models.UserSession, req ResourceAccessUpdateRequest) (*ResourceAccessGrant, error)
	DeleteResourceAccessGrant(ctx context.Context, user *models.UserSession, req ResourceAccessDeleteRequest) error
	DeleteResourceAccessGrantsByPrincipalType(ctx context.Context, user *models.UserSession, req ResourceAccessDeleteByPrincipalTypeRequest) error
	TransferResourceOwnership(ctx context.Context, user *models.UserSession, req ResourceOwnershipTransferRequest) error
}

// UserSessions reloads a user session by persisted user ID for engines that
// need to execute work as a configured user instead of the request viewer.
type UserSessions interface {
	LoadUserSession(ctx context.Context, userID string) (*models.UserSession, error)
}

// Query is the neutral query/search surface. SOQL/SOSL parsing, query locators,
// response envelopes, history-object names, and parent-reference field names
// belong above this interface. Query filters cross this boundary as structured
// predicates; formula predicates are reserved for real platform formula
// expressions and compatibility callers that have not yet grown structured IR.
type Query interface {
	// Query runs a neutral query. On a nil error the returned *QueryResult is
	// guaranteed non-nil: a query that matches no rows yields an empty, Done
	// result, never a nil pointer. Implementations MUST uphold this (normalize
	// any nil to an empty result) so consumers can use the result without a
	// per-call-site nil guard.
	Query(ctx context.Context, req queryspec.QuerySpec, user *models.UserSession) (*QueryResult, error)
	SearchWithSpec(ctx context.Context, req queryspec.SearchSpec, user *models.UserSession) ([]Record, error)
	GlobalSearchWithSpec(ctx context.Context, req queryspec.GlobalSearchSpec, user *models.UserSession) ([]SearchResult, error)
	QueryHistory(ctx context.Context, req HistoryQueryRequest, user *models.UserSession) (*HistoryQueryResult, error)
}

// BulkJobs is the neutral persisted bulk-job store surface. API dialects own
// their wire shape and query language; the platform service owns storing and
// updating job lifecycle facts.
type BulkJobs 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
}

// BulkIngest is the neutral Bulk API ingest lifecycle surface. The platform owns
// object/permission preflight, persisted job lifecycle, upload storage, batch
// creation, and durable close admission; API dialects own URL shape, JSON request
// grammar, body streaming limits, and error envelopes.
type BulkIngest interface {
	CreateIngestJob(ctx context.Context, req BulkIngestCreateRequest, user *models.UserSession) (*models.BulkJob, error)
	GetIngestJob(ctx context.Context, jobID string, user *models.UserSession) (*models.BulkJob, error)
	CloseIngestJob(ctx context.Context, jobID string, state models.BulkJobState, user *models.UserSession) (*models.BulkJob, error)
	UploadIngestData(ctx context.Context, req BulkIngestUploadRequest, user *models.UserSession) error
}

const BulkIngestMaxUploadSizeBytes int64 = 100 * 1024 * 1024

type BulkIngestCreateRequest struct {
	Object              string
	Operation           models.BulkOperation
	ExternalIDFieldName string
	APIVersion          float64
}

type BulkIngestUploadRequest struct {
	JobID         string
	Body          io.Reader
	ContentLength int64
}

type BulkIngestErrorKind string

const (
	BulkIngestErrorInvalidRequest         BulkIngestErrorKind = "InvalidRequest"
	BulkIngestErrorInvalidObject          BulkIngestErrorKind = "InvalidObject"
	BulkIngestErrorInvalidOperation       BulkIngestErrorKind = "InvalidOperation"
	BulkIngestErrorInsufficientPermission BulkIngestErrorKind = "InsufficientPermission"
	BulkIngestErrorAccessDenied           BulkIngestErrorKind = "AccessDenied"
	BulkIngestErrorNotFound               BulkIngestErrorKind = "NotFound"
	BulkIngestErrorInvalidJobState        BulkIngestErrorKind = "InvalidJobState"
	BulkIngestErrorRequestTooLarge        BulkIngestErrorKind = "RequestTooLarge"
	BulkIngestErrorInternal               BulkIngestErrorKind = "Internal"
)

type BulkIngestError struct {
	Kind    BulkIngestErrorKind
	Message string
	Cause   error
}

func (e *BulkIngestError) Error() string {
	if e == nil {
		return ""
	}
	if e.Cause != nil {
		return e.Message + ": " + e.Cause.Error()
	}
	return e.Message
}

func (e *BulkIngestError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Cause
}

func (e *BulkIngestError) InternalCondition() bool {
	return e != nil && e.Kind == BulkIngestErrorInternal
}

// MetadataDeploy is the neutral substrate used by API dialects that expose
// asynchronous metadata deploy surfaces. Dialects own package formats, XML/JSON
// component grammar, URL shape, job-id prefixes, and error envelopes. The
// platform owns deploy-status persistence and transactional record application.
type MetadataDeploy interface {
	StartMetadataDeploy(ctx context.Context) (string, error)
	CompleteMetadataDeploy(ctx context.Context, jobID string, result MetadataDeployResult) error
	GetMetadataDeployResult(ctx context.Context, jobID string) (*MetadataDeployResult, bool, error)
	ApplyMetadataDeployRecord(ctx context.Context, req MetadataDeployRecordRequest, user *models.UserSession) (*MetadataDeployRecordResult, error)
}

// MetadataDeployRecordDeleter is the narrow destructive capability consumed by
// the platform metadata-deploy service. It is separate from Records.Delete so
// ordinary data APIs cannot turn a deploy-only hard delete into a public write
// surface.
type MetadataDeployRecordDeleter interface {
	DeleteMetadataDeployRecord(ctx context.Context, objectName string, id string, requiredMatch map[string]string, user *models.UserSession) error
}

type MetadataDeployResult struct {
	Done              bool
	Status            string
	Success           bool
	ErrorMessage      string
	ComponentFailures []MetadataDeployComponentFailure
}

type MetadataDeployComponentFailure struct {
	Problem       string
	FileName      string
	ComponentType string
}

type MetadataDeployRecordOperation string

const (
	// MetadataDeployRecordOperationUpsert preserves the original metadata-deploy
	// behavior and is also the zero value for existing callers.
	MetadataDeployRecordOperationUpsert MetadataDeployRecordOperation = ""
	// MetadataDeployRecordOperationDelete deletes exactly one live record. The
	// match must include a schema-declared unique field, and zero or multiple
	// matches fail closed before mutation.
	MetadataDeployRecordOperationDelete MetadataDeployRecordOperation = "delete"
)

type MetadataDeployRecordRequest struct {
	ObjectAPIName                     string
	MatchFields                       map[string]string
	Operation                         MetadataDeployRecordOperation
	CreateRecord                      Record
	UpdateRecord                      Record
	RequiredCreateLifecycleCapability string
	RequiredUpdateLifecycleCapability string
	DiagnosticComponentType           string
}

type MetadataDeployRecordResult struct {
	Created     bool
	Deleted     bool
	Diagnostics []MetadataDeployDiagnostic
}

type MetadataDeployDiagnostic struct {
	Problem       string
	ComponentType string
}

type HistoryQueryRequest struct {
	ParentObjectAPIName string
	SelectFields        []string
	ParentID            string
	Filter              queryspec.Predicate
	Limit               int
	Offset              int
	SortDesc            bool
	ScanAll             bool
	PageSize            int
	CursorOffset        int
}

type HistoryQueryResult struct {
	TotalSize int
	Done      bool
	Rows      []HistoryRecord
}

type HistoryRecord struct {
	ID          string
	ParentID    string
	Field       string
	OldValue    *string
	NewValue    *string
	DataType    string
	CreatedDate string
	CreatedByID string
}

// Metadata is the neutral static metadata-read plane. Per-user capability facts
// live in Policy.Access; this surface exposes object shape and relationship
// facts without dialect URLs or transport envelopes.
type Metadata interface {
	Describe(ctx context.Context, objectName string) (*models.ObjectMetadata, error)
	ListObjects(ctx context.Context) ([]models.ObjectMetadata, error)
	Relationships(ctx context.Context, objectName string) ([]*models.Relationship, error)
	ChildRelationships(ctx context.Context, objectName string) ([]*models.ObjectMetadata, error)
	KeyPrefix(ctx context.Context, objectName string) (string, bool)

	// ResolveLayout resolves the single default page layout for an object given
	// the caller's neutral session and an optional record-type scope. The
	// adapter derives the (profileID, role-ancestor chain) used for assignment
	// specificity from the session; callers pass only the neutral session so no
	// dialect re-computes that scope. The result is the RAW substrate layout
	// (*models.PageLayout); dialects own the wire projection. An empty
	// recordTypeID resolves the object's default layout.
	ResolveLayout(ctx context.Context, objectName string, user *models.UserSession, recordTypeID string) (*models.PageLayout, error)

	// ResolvePicklistValuesForRecordType returns the value set one record type permits for one
	// picklist field, which is a different fact from the field's whole catalogue: a record type may
	// allow a subset, and it carries its own default.
	//
	// It is on this interface rather than discovered by assertion because a dialect's answer changes
	// meaning without it. A Salesforce-compatible UI API serves picklist values per record type, so a
	// dialect that cannot ask either serves the field's whole set under a record-type-keyed route —
	// offering values that record type forbids — or refuses the route. Neither is a behaviour to leave
	// to whether a platform happens to expose a method.
	//
	// An empty or synthetic-master recordTypeID resolves the master set, so a caller does not special
	// case an object with no concrete record types. Labels and dependency metadata stay derived from
	// the field catalogue; scoped storage owns availability and default selection only.
	ResolvePicklistValuesForRecordType(ctx context.Context, objectAPIName string, recordTypeID string, fieldAPIName string) (*models.ScopedPicklistValueSet, error)

	// ResolveRelatedListResolution returns the runtime resolution metadata for a
	// virtual related list identified by its display identity (object + lookup
	// field). The second return is false when the related list is not virtual;
	// callers fall back to the display identity in that case.
	ResolveRelatedListResolution(ctx context.Context, objectName string, lookupField string) (*RelatedListResolution, bool)

	// Actions returns live object-scoped action metadata. The metadata plane owns
	// action lookup and liveness filtering; dialects own the URL and wire DTO.
	Actions(ctx context.Context, objectName string) ([]*models.ActionMetadata, error)
	// ActionByID returns one live action metadata row by its substrate ID. The
	// metadata plane owns liveness filtering; dialects own any public action-name
	// fallback or wire projection.
	ActionByID(ctx context.Context, actionID string) (*models.ActionMetadata, error)
}

// LayoutMetadataReader is the neutral static metadata capability for page
// layout, approval layout, and assignment reads. The substrate models are
// shared metadata facts; dialects own their URL shape and response projection.
type LayoutMetadataReader interface {
	ListLayouts(ctx context.Context, objectName string) ([]*models.PageLayout, error)
	// GetLayoutByID returns the layout as it should be PRESENTED: the metadata
	// plane may strip items a renderer must not offer as editable. Callers that
	// persist what they read must use LayoutAuthoringReader instead.
	GetLayoutByID(ctx context.Context, layoutID string) (*models.PageLayout, error)
	ListLayoutAssignments(ctx context.Context, objectName string) ([]*models.LayoutAssignment, error)
	ListApprovalLayouts(ctx context.Context, objectName string) ([]*models.ApprovalPageLayout, error)
	GetApprovalLayoutByID(ctx context.Context, layoutID string) (*models.ApprovalPageLayout, error)
}

// LayoutAuthoringReader is the dedicated neutral read for page-layout authoring
// surfaces — callers that will write back what they read. It deliberately sits
// beside LayoutMetadataReader rather than extending it, because the two return
// different facts:
//
//	LayoutMetadataReader.GetLayoutByID  — the layout as it should be PRESENTED
//	LayoutAuthoringReader               — the layout as it is STORED
//
// The presentation read may strip items a renderer must not offer as editable.
// A surface that reads through it and then persists what it read writes that
// stripping back as a deletion, which is how authored layout content has been
// lost in the past. Anything that saves what it read must use this interface.
//
// Being a separate interface is also what keeps a dialect pinned to an older
// revision working against a newer platform. Dialects bind these capabilities
// through comma-ok type assertions, so an implementation built to the older
// shape — including a pinned dialect's own test double — silently yields nil
// for any interface it no longer satisfies, and the dialect then fails its
// whole UI API closed. Measured against the pinned revision: moving this method
// onto LayoutMetadataReader leaves `go build` and `go vet` clean while turning
// 25 dialect tests red, because the double loses the entire reader; as its own
// interface, the same pin is untouched.
//
// The reverse direction is not symmetric and is not a runtime concern: a
// dialect that references this interface will not compile against a platform
// that lacks it.
//
// One documented exception to verbatim: IsDefault is re-derived from the live
// wildcard layout assignment, its only source of truth, so callers must not
// echo it back into a write.
type LayoutAuthoringReader interface {
	GetLayoutForAuthoring(ctx context.Context, layoutID string) (*models.PageLayout, error)
}

// ObjectMetadataCatalogReader exposes additional neutral catalog facts that are
// not dialect presentation by themselves but are needed by richer API dialects.
type ObjectMetadataCatalogReader interface {
	ListViews(ctx context.Context, objectName string, user *models.UserSession) ([]*models.ListView, error)
	RecordTypes(ctx context.Context, objectName string) ([]*models.RecordType, error)
}

// ListViewAuthoring is the dedicated neutral authoring surface for app-facing
// private list-view writes. It deliberately sits beside generic Records so
// callers do not encode list-view write rules through _System_ListView CRUD.
type ListViewAuthoring interface {
	CreatePrivate(ctx context.Context, user *models.UserSession, input *models.ListView) (*models.ListView, error)
	UpdatePrivate(ctx context.Context, user *models.UserSession, id string, patch models.ListViewPatch) (*models.ListView, error)
	DeletePrivate(ctx context.Context, user *models.UserSession, id string) error
	UpsertPrivateByDeveloperName(ctx context.Context, user *models.UserSession, objectAPIName string, developerName string, input *models.ListView, patch models.ListViewPatch) (*models.ListView, bool, error)
}

// MetadataAuthoring is the neutral authoring plane for object, field,
// validation-rule, action, and layout-assignment metadata. The implementation
// owns the platform side effects that are part of authoring (permission
// seeding, cache invalidation, formula-view rebuild/rollback, app navigation
// cleanup, and layout-assignment normalization). Dialects own request payload
// aliases, URL shape, and response envelopes.
type MetadataAuthoring interface {
	ListSchemasForAuthoring(ctx context.Context) []*models.ObjectMetadata
	GetEffectiveSchemaForAuthoring(ctx context.Context, apiName string, user *models.UserSession) *models.ObjectMetadata
	SharingParentRelationship(ctx context.Context, apiName string) *models.Relationship

	CreateSchema(ctx context.Context, req MetadataSchemaCreateRequest) (*models.ObjectMetadata, error)
	UpdateSchema(ctx context.Context, apiName string, updates *models.ObjectMetadata) error
	DeleteSchema(ctx context.Context, apiName string) error
	RefreshAuthoringMetadataCache(ctx context.Context) error

	CreateField(ctx context.Context, objectAPIName string, field *models.FieldMetadata) error
	BatchCreateFields(ctx context.Context, objectAPIName string, fields []models.FieldMetadata) ([]*models.FieldMetadata, error)
	UpdateField(ctx context.Context, objectAPIName string, fieldAPIName string, updates *models.FieldMetadata, presence FieldPatchPresence) error
	DeleteField(ctx context.Context, objectAPIName string, fieldAPIName string) error

	ListValidationRules(ctx context.Context, objectAPIName string) []models.ValidationRule
	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

	CreateAction(ctx context.Context, action *models.ActionMetadata) error
	CreateLayoutAssignment(ctx context.Context, assignment *models.LayoutAssignment) error
}

type MetadataSchemaCreateRequest struct {
	Schema *models.ObjectMetadata
	AppID  *string
}

type FieldPatchPresence struct {
	RequiredProvided     bool
	UniqueProvided       bool
	TrackHistoryProvided bool
	// ReadProtectionPolicyProvided is true when the caller explicitly included
	// readProtectionPolicy in a PATCH payload, including an explicit empty
	// value used to clear metadata.
	ReadProtectionPolicyProvided bool
}

type MetadataAuthoringErrorKind string

const (
	MetadataAuthoringErrorValidation MetadataAuthoringErrorKind = "validation"
	MetadataAuthoringErrorNotFound   MetadataAuthoringErrorKind = "not_found"
	MetadataAuthoringErrorForbidden  MetadataAuthoringErrorKind = "forbidden"
	MetadataAuthoringErrorConflict   MetadataAuthoringErrorKind = "conflict"
	MetadataAuthoringErrorInternal   MetadataAuthoringErrorKind = "internal"
	MetadataAuthoringErrorServer     MetadataAuthoringErrorKind = "server"
)

type MetadataAuthoringError struct {
	Kind    MetadataAuthoringErrorKind
	Field   string
	Message string
}

func (e *MetadataAuthoringError) Error() string {
	if e == nil {
		return ""
	}
	return e.Message
}

// MetadataMaintenance is the neutral process-local metadata maintenance surface
// available to API dialects. It owns cache refresh mechanics and derived-engine
// reconciliation; dialects own URL shape and response envelopes.
type MetadataMaintenance interface {
	RefreshAutoNumberMetadata(ctx context.Context) (*AutoNumberMetadataRefreshResult, error)
	FormulaViewStatus(ctx context.Context, objectName string) (*FormulaViewStatusResult, error)
	PreviewFormulaViewDDL(ctx context.Context, objectName string) (*FormulaViewDDLResult, error)
	RebuildFormulaView(ctx context.Context, objectName string) (*FormulaViewRebuildResult, error)
	RebuildAllFormulaViews(ctx context.Context) (*FormulaViewRebuildAllResult, error)
	ValidateFormulaView(ctx context.Context, req FormulaViewValidationRequest) (*FormulaViewValidationResult, error)
}

type AutoNumberMetadataRefreshResult struct {
	RuleCount            int
	QuarantinedAfterSync int
	InProcess            bool
}

type FormulaViewStatusResult struct {
	Object             string
	ViewExists         bool
	ViewName           string
	ActiveFormulaCount int
	FrozenFormulaCount int
	ResolvedTableName  string
}

type FormulaViewDDLResult struct {
	Object string
	DDL    string
}

type FormulaViewRebuildResult struct {
	Object   string
	ViewName string
}

type FormulaViewRebuildAllResult struct{}

type FormulaViewValidationRequest struct {
	ObjectName   string
	Formula      string
	ReturnType   string
	FieldAPIName string
}

type FormulaViewValidationResult struct {
	Valid            bool
	Error            string
	CompiledSQL      string
	ReferencedFields []string
	CrossTableRefs   []string
}

type MetadataMaintenanceErrorKind string

const (
	MetadataMaintenanceErrorUnavailable MetadataMaintenanceErrorKind = "unavailable"
	MetadataMaintenanceErrorInvalid     MetadataMaintenanceErrorKind = "invalid"
	MetadataMaintenanceErrorNotFound    MetadataMaintenanceErrorKind = "not_found"
	MetadataMaintenanceErrorInternal    MetadataMaintenanceErrorKind = "internal"
)

type MetadataMaintenanceError struct {
	Kind    MetadataMaintenanceErrorKind
	Message string
	Cause   error
}

func (e *MetadataMaintenanceError) Error() string {
	if e == nil {
		return ""
	}
	if e.Message != "" {
		return e.Message
	}
	return string(e.Kind)
}

func (e *MetadataMaintenanceError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Cause
}

// RelatedListResolution is the neutral mirror of the runtime resolution metadata
// for virtual related lists whose display identity differs from their
// query/navigation targets. Field names and JSON tags are byte-identical to the
// metadata substrate type. This is a runtime-only contract — never persisted.
type RelatedListResolution struct {
	Query  ResolvedQueryTarget  `json:"query"`
	Detail ResolvedDetailTarget `json:"detail"`
	List   ResolvedListTarget   `json:"list"`
	Create ResolvedCreateTarget `json:"create"`
}

// ResolvedQueryTarget is the SObject + lookup field used for COUNT and SELECT queries.
type ResolvedQueryTarget struct {
	ObjectApiName      string `json:"objectApiName"`
	LookupField        string `json:"lookupField"`
	ParentFieldApiName string `json:"parentFieldApiName,omitempty"`
}

// ResolvedDetailTarget is the SObject used when navigating to a record detail page.
type ResolvedDetailTarget struct {
	ObjectApiName string `json:"objectApiName"`
}

// ResolvedListTarget is the SObject + filter field used for "View All" navigation.
type ResolvedListTarget struct {
	ObjectApiName      string `json:"objectApiName"`
	FilterField        string `json:"filterField"`
	ParentFieldApiName string `json:"parentFieldApiName,omitempty"`
}

// ResolvedCreateTarget describes the "New" button behavior.
// Kind: "standard" (record creation form), "upload" (file upload), "none" (hide button).
type ResolvedCreateTarget struct {
	Kind            string            `json:"kind"`                      // "standard" | "upload" | "none"
	ObjectApiName   string            `json:"objectApiName,omitempty"`   // target object for "standard" create
	PrefillMappings map[string]string `json:"prefillMappings,omitempty"` // targetField -> parent/source field
}

// FlowExecutor is the neutral behavior-plane surface for direct flow execution
// and interview resume. Dialects own the final wire encoding.
type FlowExecutor interface {
	ExecuteFlow(ctx context.Context, flowAPIName string, inputs map[string]any, user *models.UserSession) (*FlowExecutionResult, error)
	ResumeInterview(ctx context.Context, interviewID string, user *models.UserSession) error
}

// FlowCatalog is the neutral catalog surface for Flow metadata decisions that
// are not execution. Dialects use it to hide host-owned/protected Flows from
// public action surfaces without reading platform tables directly.
type FlowCatalog interface {
	ProtectedFlowNames(ctx context.Context) ([]string, error)
	ListFlows(ctx context.Context) ([]FlowDescriptor, error)
	GetFlow(ctx context.Context, flowName string) (*FlowDescriptor, error)
	GetFlowRevision(ctx context.Context, revisionID string) (*FlowRevisionDescriptor, error)
}

type FlowDescriptor struct {
	Label            string
	Name             string
	Description      string
	ActiveRevisionID string
}

type FlowRevisionDescriptor struct {
	Variables []FlowVariableDescriptor
}

type FlowVariableDescriptor struct {
	Name     string
	DataType string
	IsInput  bool
	IsOutput bool
}

// FlowInterviews is the neutral runtime surface for persisted Flow interview
// rows. Dialects own any sObject/Tooling URLs and JSON field casing.
type FlowInterviews interface {
	ListFlowInterviews(ctx context.Context, user *models.UserSession) ([]FlowInterviewRecord, error)
	GetFlowInterview(ctx context.Context, interviewID string, user *models.UserSession) (*FlowInterviewRecord, error)
	DeleteFlowInterview(ctx context.Context, interviewID string, user *models.UserSession) error
}

type FlowInterviewRecord struct {
	ID              string
	FlowRevisionID  string
	InterviewLabel  string
	InterviewStatus string
	CurrentElement  string
	OwnerID         string
	CreatedDate     time.Time
	PausedDate      *time.Time
	PausedUntil     *time.Time
}

// FlowAuthoring is a transitional Flow metadata authoring surface. It still
// carries Salesforce-shaped Flow definition/version fields until a real neutral
// Flow authoring IR exists, but wire-specific sparse PATCH decoding belongs to
// the dialect before calls reach this contract.
type FlowAuthoring interface {
	ListFlowDefinitions(ctx context.Context) ([]FlowDefinitionRecord, error)
	GetFlowDefinition(ctx context.Context, id string) (*FlowDefinitionRecord, error)
	CreateFlowDefinition(ctx context.Context, def FlowDefinitionWrite) (*FlowDefinitionCreateResult, error)
	PatchFlowDefinition(ctx context.Context, id string, patch FlowAdminDefinitionPatchIntent) error
	DeleteFlowDefinition(ctx context.Context, id string) error

	ListFlowVersions(ctx context.Context, definitionID string) ([]FlowVersionRecord, error)
	GetFlowVersion(ctx context.Context, id string) (*FlowVersionRecord, error)
	CreateFlowVersion(ctx context.Context, version FlowVersionWrite) (*FlowVersionCreateResult, error)
	PatchFlowVersion(ctx context.Context, id string, patch FlowAdminVersionPatchIntent) error
	DeleteFlowVersion(ctx context.Context, id string) error
	ActivateFlowVersion(ctx context.Context, id string) error
	DeactivateFlowVersion(ctx context.Context, id string) error
}

type FlowDefinitionRecord struct {
	ID                   string
	DeveloperName        string
	MasterLabel          string
	Description          string
	ProcessType          string
	TriggerType          string
	TriggerObjectOrEvent string
	ActiveVersionID      string
	LatestVersionID      string
	CreatedDate          string
	LastModifiedDate     string
	TriggerOrder         *int
}

type FlowDefinitionWrite struct {
	ID                   string
	DeveloperName        string
	MasterLabel          string
	Description          string
	ProcessType          string
	TriggerType          string
	TriggerObjectOrEvent string
	ActiveVersionID      string
	LatestVersionID      string
}

type FlowDefinitionCreateResult struct {
	ID string
}

type FlowAdminDefinitionPatchAction string

const (
	FlowAdminDefinitionPatchActionUpdate          FlowAdminDefinitionPatchAction = "update"
	FlowAdminDefinitionPatchActionActivateVersion FlowAdminDefinitionPatchAction = "activate_version"
	FlowAdminDefinitionPatchActionDeactivate      FlowAdminDefinitionPatchAction = "deactivate"
)

type FlowAdminDefinitionPatchIntent struct {
	Action              FlowAdminDefinitionPatchAction
	Name                *string
	Label               *string
	Description         *string
	ActiveVersionNumber int
}

type FlowVersionRecord struct {
	ID               string
	DefinitionID     string
	MasterLabel      string
	VersionNumber    int
	Status           string
	Description      string
	ProcessType      string
	MetadataRaw      []byte
	CreatedDate      string
	LastModifiedDate string
}

type FlowVersionWrite struct {
	ID           string
	DefinitionID string
	MasterLabel  string
	Status       string
	Description  string
	ProcessType  string
	MetadataRaw  []byte
}

type FlowVersionCreateResult struct {
	ID            string
	VersionNumber int
}

type FlowAdminVersionPatchAction string

const (
	FlowAdminVersionPatchActionUpdate     FlowAdminVersionPatchAction = "update"
	FlowAdminVersionPatchActionActivate   FlowAdminVersionPatchAction = "activate"
	FlowAdminVersionPatchActionDeactivate FlowAdminVersionPatchAction = "deactivate"
)

type FlowAdminVersionPatchIntent struct {
	Action              FlowAdminVersionPatchAction
	Description         string
	DescriptionProvided bool
	MetadataPatchRaw    []byte
}

type FlowAuthoringErrorKind string

const (
	FlowAuthoringErrorFieldDenied                   FlowAuthoringErrorKind = "field_denied"
	FlowAuthoringErrorDuplicateVersion              FlowAuthoringErrorKind = "duplicate_version"
	FlowAuthoringErrorDuplicateDefinitionDeveloper  FlowAuthoringErrorKind = "duplicate_definition_developer_name"
	FlowAuthoringErrorDuplicateAutoCreatedDeveloper FlowAuthoringErrorKind = "duplicate_auto_created_developer_name"
	FlowAuthoringErrorLockContention                FlowAuthoringErrorKind = "lock_contention"
	FlowAuthoringErrorInternal                      FlowAuthoringErrorKind = "internal"
)

type FlowAuthoringError struct {
	Kind          FlowAuthoringErrorKind
	Message       string
	Fields        []string
	DeveloperName string
	Cause         error
}

func (e *FlowAuthoringError) Error() string {
	if e == nil {
		return ""
	}
	if e.Message != "" {
		return e.Message
	}
	return string(e.Kind)
}

func (e *FlowAuthoringError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Cause
}

type FlowExecutionResult struct {
	ActionName    string
	IsSuccess     bool
	Errors        []FlowExecutionError
	OutputValues  map[string]any
	IsCustomError bool
	IsRetriable   bool
}

type FlowExecutionError struct {
	Code    string
	Message string
	Fields  []string
}

// FlowExecutionErrorCodeValidation is the neutral L0 condition for a
// client-visible validation rejection. Product dialects own the final wire
// spelling for this condition.
const FlowExecutionErrorCodeValidation = "VALIDATION_ERROR"

// FlowExecutionErrorCodeFieldWriteDenied is the neutral L0 condition for a
// record mutation rejected because one or more fields are not writable. Product
// dialects own the final wire spelling for this condition.
const FlowExecutionErrorCodeFieldWriteDenied = "FIELD_WRITE_DENIED"

type ApprovalSubmitRequest struct {
	ObjectAPIName     string
	RecordID          string
	Comments          string
	SkipEntryCriteria bool
}

type ApprovalPendingView struct {
	WorkItemID          string
	ProcessInstanceID   string
	ProcessDefinitionID string
	TargetObjectID      string
	ObjectAPIName       string
	SubmittedByID       string
	SubmittedByName     string
	SubmittedDate       string
	ActorID             string
	OriginalActorID     string
	Comments            string
	RecordName          string
}

type ApprovalProcessInstancePendingWorkItem struct {
	ID                string
	ActorID           string
	ActorName         string
	OriginalActorID   string
	OriginalActorName string
}

type ApprovalProcessInstanceSummary struct {
	ID                  string
	ProcessDefinitionID string
	TargetObjectID      string
	Status              string
	SubmittedByID       string
	SubmittedByName     string
	CreatedDate         string
	CompletedDate       string
	PendingWorkItems    []ApprovalProcessInstancePendingWorkItem
}

type ApprovalStepDisplayMetadata struct {
	ProcessNodeID    string
	StepOrder        int
	StepName         string
	StepLabel        string
	HasEntryCriteria bool
	IfCriteriaNotMet string
}

type ApprovalProcessActionFailure struct {
	ActionName   string
	ActionType   string
	TargetObject string
	TargetID     string
	Field        string
	Reason       string
}

type Approvals interface {
	Submit(ctx context.Context, req ApprovalSubmitRequest, user *models.UserSession) (Record, error)
	Approve(ctx context.Context, workItemID, comments string, user *models.UserSession) (Record, error)
	Reject(ctx context.Context, workItemID, comments string, user *models.UserSession) (Record, error)
	Recall(ctx context.Context, workItemID, comments string, user *models.UserSession) (Record, error)
	Reassign(ctx context.Context, workItemID, newActorID string, user *models.UserSession) error
	ListPendingApprovals(ctx context.Context, user *models.UserSession) ([]ApprovalPendingView, error)
	GetLatestProcessInstance(ctx context.Context, recordID string, user *models.UserSession) (*ApprovalProcessInstanceSummary, error)
	GetRecordApprovalStepDisplayMetadata(ctx context.Context, recordID string, user *models.UserSession) ([]ApprovalStepDisplayMetadata, error)
	Clone(ctx context.Context, processDefID, newProcessName, newLabel string, user *models.UserSession) (Record, error)
	ListApprovalNotificationFlows(ctx context.Context) ([]FlowDescriptor, error)
}

// Policy materializes caller-effective access facts below dialect mappers. Callers
// choose the projection depth they need through AccessRequest; object CRUD,
// field access, and record-type availability stay behind this one authoritative
// read path.
type Policy interface {
	Access(ctx context.Context, user *models.UserSession, objectName string, req AccessRequest) (*EffectiveObjectAccess, error)
}

type Capability = constants.Permission

// CapabilityPolicy extends Policy with neutral platform capability checks.
// Consumers that need capability decisions should depend on this extended
// contract; describe/read-only policy fakes can keep implementing Policy alone.
type CapabilityPolicy interface {
	Policy
	HasCapability(ctx context.Context, user *models.UserSession, capability Capability) bool
}

// Notifications is the neutral outbound-notification capability exposed to
// product dialects. The platform owns transport, delivery config, and host URLs;
// dialects own product-specific message content and trigger semantics.
type Notifications interface {
	SendEmail(ctx context.Context, msg *OutboundEmailMessage) (*OutboundEmailResult, error)
	FrontendBaseURL(ctx context.Context) string
}

type OutboundEmailMessage struct {
	To      []string
	Cc      []string
	Bcc     []string
	From    string
	Subject string
	Body    string
	HTML    string
}

type OutboundEmailRecipientResult struct {
	Address string
	Role    string
	Status  string
	Error   string
}

type OutboundEmailResult struct {
	SentCount        int
	RecipientResults []OutboundEmailRecipientResult
	MessageID        string
}

type AccessRequest struct {
	IncludeFields      bool
	IncludeRecordTypes bool
}

type ObjectAccess struct {
	Create     bool
	Read       bool
	Update     bool
	Delete     bool
	Deactivate bool
}

type FieldAccess struct {
	Createable bool
	Updateable bool
	Readable   bool
}

type RecordTypeAvailability struct {
	ID        string
	APIName   string
	Name      string
	Available bool
	Master    bool
	Default   bool
}

type EffectiveObjectAccess struct {
	Object                 ObjectAccess
	Fields                 map[string]FieldAccess
	RecordTypes            []RecordTypeAvailability
	DefaultRecordTypeID    string
	HasConcreteRecordTypes bool
	AvailableConcreteCount int
}

// StandardActionInputGuard carries one evaluated input-guard verdict. The
// condition and permission names are metadata facts; dialects decide how much
// of this discovery payload to expose on their wire.
type StandardActionInputGuard struct {
	GatedInput          string
	RequiredPermission  string
	RequiredObject      string
	RequiredOperation   string
	Condition           string
	CallerHasPermission bool
}

// StandardActionSchemaField describes one metadata-declared action input or
// output. It is URL-free and transport-free; dialects project it into their
// own discovery DTOs.
type StandardActionSchemaField struct {
	Name                      string
	Type                      string
	Required                  bool
	ReferenceTo               string
	Label                     string
	UISection                 string
	UIMode                    string
	TargetObject              string
	TargetFieldAPIName        string
	RequiredPicklistTag       string
	MappingSourceObject       string
	MappingActionType         string
	PrefillSourceFieldAPIName string
	IsCollection              bool
	TargetRequired            bool
	TargetDefaulted           bool
	Defaulted                 bool
	HasDefaultValue           bool
	DefaultValue              any
}

// StandardActionDescriptor is the discovery result for one invokable standard
// action. Endpoint URLs are intentionally absent; they are dialect vocabulary.
type StandardActionDescriptor struct {
	Name                   string
	Label                  string
	Description            string
	Category               string
	FlowName               string
	IsAtomic               bool
	RequiredPermissions    []string
	TargetObjects          []string
	RendererID             string
	VisibilityCondition    string
	PreviewFlowName        string
	HeaderFallbackEligible bool
	CreatePreviewSupported bool
	InputGuards            []StandardActionInputGuard
	Inputs                 []StandardActionSchemaField
	Outputs                []StandardActionSchemaField
}

type StandardActionCreatePreviewImage struct {
	Object                    string
	Fields                    Record
	SiblingMaxDefaultedFields []string
}

type StandardActionChildPreview struct {
	Object string
	Count  int
	Rows   []Record
}

type StandardActionPreviewResult struct {
	ActionName    string
	IsSuccess     bool
	Errors        []StandardActionError
	ParentPreview *StandardActionCreatePreviewImage
	ChildPreview  []StandardActionChildPreview
}

type StandardActionOutputPreview struct {
	OutputValues map[string]any
	IsSuccess    bool
	Errors       []StandardActionError
}

type StandardActionInvocationResult struct {
	ActionName   string
	IsSuccess    bool
	Errors       []StandardActionError
	OutputValues map[string]any
}

type StandardActionError struct {
	Code    string
	Message string
	Fields  []string
}

type StandardActionOutcomeKind string

const (
	StandardActionOutcomeDisabled           StandardActionOutcomeKind = "disabled"
	StandardActionOutcomeNotRegistered      StandardActionOutcomeKind = "not_registered"
	StandardActionOutcomeUnauthorized       StandardActionOutcomeKind = "unauthorized"
	StandardActionOutcomeInvalidRequest     StandardActionOutcomeKind = "invalid_request"
	StandardActionOutcomePreviewUnsupported StandardActionOutcomeKind = "preview_unsupported"
	StandardActionOutcomeNoPreviewProducer  StandardActionOutcomeKind = "no_preview_producer"
	StandardActionOutcomeInternal           StandardActionOutcomeKind = "internal"
	StandardActionOutcomeTimeout            StandardActionOutcomeKind = "timeout"
)

// StandardActionOutcome is a neutral request-level abort. Per-input rejects do
// not use this type; they remain failed invocation/preview results inside the
// successful request body so batch isolation is preserved.
type StandardActionOutcome struct {
	Kind    StandardActionOutcomeKind
	Code    string
	Name    string
	Message string
	Cause   error
}

func (o *StandardActionOutcome) Error() string {
	if o == nil {
		return ""
	}
	if o.Message != "" {
		return o.Message
	}
	return string(o.Kind)
}

type StandardActionState string

const (
	StandardActionAbsent   StandardActionState = "absent"
	StandardActionActive   StandardActionState = "active"
	StandardActionDisabled StandardActionState = "disabled"
)

// StandardActionRef is the resolved registry handle for an active standard
// action. Dialects may inspect State and Name for their preflight gates and
// envelopes, but Binding is engine-owned and opaque: dialects must pass it back
// verbatim to the resolved service methods, never inspect or construct it.
type StandardActionRef struct {
	State   StandardActionState
	Name    string
	Binding any
}

type StandardActionResolution struct {
	State                    StandardActionState
	HasOutputPreviewProducer bool
	Action                   *StandardActionRef
}

// StandardActionService is the neutral behavior surface for registry-backed
// standard actions. Dialect-specific fallbacks stay above this interface.
type StandardActionService interface {
	ResolveStandardAction(ctx context.Context, actionName string) (*StandardActionResolution, error)
	AuthorizeResolvedStandardAction(ctx context.Context, action *StandardActionRef, user *models.UserSession) *StandardActionOutcome
	ListStandardActions(ctx context.Context, user *models.UserSession) ([]StandardActionDescriptor, error)
	InvokeResolvedStandardAction(ctx context.Context, action *StandardActionRef, inputs []map[string]any, user *models.UserSession) ([]StandardActionInvocationResult, *StandardActionOutcome)
	PreviewResolvedRecordCreate(ctx context.Context, action *StandardActionRef, input map[string]any, user *models.UserSession) (*StandardActionPreviewResult, *StandardActionOutcome)
	PreviewResolvedOutputs(ctx context.Context, action *StandardActionRef, recordID string, user *models.UserSession) (*StandardActionOutputPreview, *StandardActionOutcome)
}

// Deps is the bundled neutral surface a dialect should be expressed over. It is
// intentionally a struct of interfaces rather than concrete engines.
type Deps struct {
	Records                   Records
	RecordMutationCoordinator RecordMutationCoordinator
	RecycleBin                RecycleBin
	AuditLog                  AuditLog
	Query                     Query
	BulkJobs                  BulkJobs
	BulkIngest                BulkIngest
	MetadataDeploy            MetadataDeploy
	Reports                   Reports
	FolderAccess              FolderAccess
	ResourceAccess            ResourceAccess
	UserSessions              UserSessions
	Metadata                  Metadata
	ListViewAuthoring         ListViewAuthoring
	MetadataAuthoring         MetadataAuthoring
	ApplicationNavigation     ApplicationNavigation
	MetadataMaintenance       MetadataMaintenance
	RollupMaintenance         RollupMaintenance
	Policy                    Policy
	Notifications             Notifications
	PermissionAdmin           PermissionAdmin
	ConnectorSyncStatus       ConnectorSyncStatus
	ConnectorWebhookSecrets   ConnectorWebhookSecrets
	SearchScope               SearchScope
	Activity                  Activity
	Files                     Files
	RecordAttachmentMetadata  RecordAttachmentMetadataAuthorizer
	FlowExecutor              FlowExecutor
	FlowCatalog               FlowCatalog
	FlowInterviews            FlowInterviews
	FlowAuthoring             FlowAuthoring
	Approvals                 Approvals
	StandardActions           StandardActionService
}
