package contract

import (
	"context"

	"github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

// RecordLifecyclePhase identifies a generic, transaction-scoped lifecycle
// point exposed by the L0 records engine. The phase names are deliberately
// app-neutral; app packs own which object/capability hooks they register.
type RecordLifecyclePhase string

const (
	RecordLifecycleAfterCreateInTx RecordLifecyclePhase = "after_create_in_tx"
	RecordLifecycleAfterUpdateInTx RecordLifecyclePhase = "after_update_in_tx"
	RecordLifecycleAfterDeleteInTx RecordLifecyclePhase = "after_delete_in_tx"

	// AssignmentPolicyEligibilityLifecycleCapability is the app-neutral L0
	// capability that interprets an app-authored eligibility projection from
	// the same immutable lifecycle policy as the source record mutation.
	AssignmentPolicyEligibilityLifecycleCapability = "platform.assignment-policy-eligibility"
)

// RecordLifecycleEvent is the neutral event shape delivered to lifecycle
// hooks registered outside the records package.
type RecordLifecycleEvent struct {
	ObjectName  string
	RecordID    string
	Record      Record
	OldRecord   Record
	CurrentUser *models.UserSession
}

// RecordLifecycleHook handles one lifecycle event. Implementations must treat
// the call as part of the surrounding write transaction: returning an error
// fails the write.
type RecordLifecycleHook func(context.Context, RecordLifecycleEvent) error

// RecordLifecycleBatchEvent retains the phase for one event collected across
// a complete records-owned transaction attempt. This lets one capability plan
// and acquire the union of its locks before applying any transaction-bound
// effects, even when that transaction contains mixed create/update/delete
// operations.
type RecordLifecycleBatchEvent struct {
	Phase  RecordLifecyclePhase
	Event  RecordLifecycleEvent
	Policy *models.RecordLifecycleAccessPolicy
}

// RecordLifecycleBatchHook handles every event for one capability once, after
// the records-owned transaction callback succeeds and before it may commit.
// Returning an error fails the whole transaction attempt.
type RecordLifecycleBatchHook func(context.Context, []RecordLifecycleBatchEvent) error

// RecordLifecycleRegistrar is the neutral registration seam for lifecycle
// behavior owned outside the L0 records engine. Registrations are keyed by
// metadata-declared capability, not by concrete object names.
type RecordLifecycleRegistrar interface {
	RegisterRecordLifecycleHook(RecordLifecycleRegistration) error
}

// RecordLifecycleBatchRegistrar is the optional transaction-batched lifecycle
// registration seam. It is separate from RecordLifecycleRegistrar so existing
// single-event registrars and product integrations remain source compatible.
type RecordLifecycleBatchRegistrar interface {
	RegisterRecordLifecycleBatchHook(RecordLifecycleBatchRegistration) error
}

// RecordLifecycleCoordinator exposes the transaction, before-save event,
// after-save lifecycle hook, and after-save outbox pipeline needed by non-record
// substrate authoring services that intentionally mirror records-engine write
// semantics without becoming generic records.
type RecordLifecycleCoordinator interface {
	RunInPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error
	PublishRecordLifecycleEvent(ctx context.Context, eventType events.EventType, objectName string, record Record, oldRecord *Record, user *models.UserSession) error
	StageRecordAfterSaveOldProjection(ctx context.Context, objectName string, recordID string, user *models.UserSession) (context.Context, error)
	RunRecordLifecycleHooks(ctx context.Context, phase RecordLifecyclePhase, event RecordLifecycleEvent) error
	PublishRecordAfterSaveEvent(ctx context.Context, eventType events.EventType, objectName string, record Record, oldRecord *Record, user *models.UserSession) error
}

// RecordLifecycleRegistration declares one hook binding. L1 code registers a
// handler for a metadata-declared lifecycle capability, not for a concrete
// object name. Object-to-capability ownership lives in object metadata.
type RecordLifecycleRegistration struct {
	Capability string
	Phase      RecordLifecyclePhase
	// RequiresPessimisticTransaction makes direct records-engine writes enter
	// a pessimistic boundary before dispatching this binding.
	RequiresPessimisticTransaction bool
	Hook                           RecordLifecycleHook
}

// RecordLifecycleBatchRegistration binds one capability across one or more
// phases to one transaction-batched handler. Metadata still opts each object
// into the capability independently for each phase.
type RecordLifecycleBatchRegistration struct {
	Capability string
	Phases     []RecordLifecyclePhase
	// RequiresPessimisticTransaction makes direct records-engine writes enter
	// a pessimistic boundary before collecting this binding.
	RequiresPessimisticTransaction bool
	Hook                           RecordLifecycleBatchHook
}

type recordLifecycleSuppressedKey struct{}

// WithRecordLifecycleHooksSuppressed marks a context whose writes should not
// dispatch metadata-declared lifecycle hooks. It is intentionally coarse:
// callers use it only when they own an alternate, explicit write path for the
// same fact in the surrounding transaction.
func WithRecordLifecycleHooksSuppressed(ctx context.Context) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, recordLifecycleSuppressedKey{}, true)
}

// RecordLifecycleHooksSuppressed reports whether lifecycle hooks are suppressed
// for the current write context.
func RecordLifecycleHooksSuppressed(ctx context.Context) bool {
	if ctx == nil {
		return false
	}
	suppressed, _ := ctx.Value(recordLifecycleSuppressedKey{}).(bool)
	return suppressed
}

// RecordLifecycleDiagnostic is a non-fatal lifecycle diagnostic. A hook may
// report diagnostics when the write should still commit but a higher-level
// import/deploy surface needs to expose per-component warnings or failures.
type RecordLifecycleDiagnostic struct {
	Problem       string
	ComponentType string
}

type recordLifecycleDiagnosticReporterKey struct{}

type recordLifecycleDiagnosticReporter func(RecordLifecycleDiagnostic)

// WithRecordLifecycleDiagnosticReporter installs a non-fatal diagnostic sink
// for lifecycle hooks running under ctx.
func WithRecordLifecycleDiagnosticReporter(ctx context.Context, report func(RecordLifecycleDiagnostic)) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	if report == nil {
		return ctx
	}
	return context.WithValue(ctx, recordLifecycleDiagnosticReporterKey{}, recordLifecycleDiagnosticReporter(report))
}

// ReportRecordLifecycleDiagnostic emits a lifecycle diagnostic when the current
// context has a reporter. It returns false when there is no reporter.
func ReportRecordLifecycleDiagnostic(ctx context.Context, diagnostic RecordLifecycleDiagnostic) bool {
	if ctx == nil {
		return false
	}
	report, _ := ctx.Value(recordLifecycleDiagnosticReporterKey{}).(recordLifecycleDiagnosticReporter)
	if report == nil {
		return false
	}
	report(diagnostic)
	return true
}
