package constants

import (
	"context"
	"strings"
)

type previewContextKey string

const (
	contextKeyInsertPreviewPreparation previewContextKey = "insert_preview_preparation"
	contextKeyUpdatePreviewPreparation previewContextKey = "update_preview_preparation"
	contextKeyPreviewSyntheticRecords  previewContextKey = "preview_synthetic_records"
	contextKeyPreviewSiblingMaxFields  previewContextKey = "preview_sibling_max_defaulted_fields"
	contextKeyPreviewSiblingMaxToken   previewContextKey = "preview_sibling_max_defaulted_token"
)

// PreviewSyntheticRecord is a request-scoped no-DML record image produced by a
// create preview. It exists so Flow output-reference wiring can remain
// coherent inside one preview run; it is not a durable-record existence oracle.
type PreviewSyntheticRecord struct {
	ObjectName string
	Fields     map[string]interface{}
}

// PreviewSyntheticRecordRegistry stores preview-created records by synthetic
// ID. Runtime-minted IDs include per-run entropy so caller-supplied literals
// cannot predictably collide with this registry. The registry is intentionally
// mutable through context, but records validators and read paths must still
// fail closed unless they explicitly implement synthetic-row support.
type PreviewSyntheticRecordRegistry struct {
	records map[string]PreviewSyntheticRecord
}

// PreviewSiblingMaxDefaultedFieldRegistry stores request-local provenance for
// no-DML create previews. Records preview preparation writes to the current
// token only after it succeeds, and automation consumes that token when it
// captures the corresponding FlowRecordCreatePreview.
type PreviewSiblingMaxDefaultedFieldRegistry struct {
	fields map[string][]string
}

// WithInsertPreviewPreparation marks a no-DML persistence insert preparation
// run. Automation uses this to allow before-save $Record mutations while still
// rejecting generic preview recordUpdate side effects.
func WithInsertPreviewPreparation(ctx context.Context) context.Context {
	return context.WithValue(ctx, contextKeyInsertPreviewPreparation, true)
}

// HasInsertPreviewPreparation reports whether ctx is running the persistence
// no-DML insert preparation path.
func HasInsertPreviewPreparation(ctx context.Context) bool {
	v, _ := ctx.Value(contextKeyInsertPreviewPreparation).(bool)
	return v
}

// WithUpdatePreviewPreparation marks a no-DML persistence update preparation
// run. Automation uses this to allow before-save $Record mutations while still
// rejecting cross-record side effects.
func WithUpdatePreviewPreparation(ctx context.Context) context.Context {
	return context.WithValue(ctx, contextKeyUpdatePreviewPreparation, true)
}

// HasUpdatePreviewPreparation reports whether ctx is running the persistence
// no-DML update preparation path.
func HasUpdatePreviewPreparation(ctx context.Context) bool {
	v, _ := ctx.Value(contextKeyUpdatePreviewPreparation).(bool)
	return v
}

// HasRecordPreviewPreparation reports whether ctx is running a no-DML
// persistence preparation path for a record write.
func HasRecordPreviewPreparation(ctx context.Context) bool {
	return HasInsertPreviewPreparation(ctx) || HasUpdatePreviewPreparation(ctx)
}

// WithPreviewSyntheticRecordRegistry attaches an empty registry for a no-DML
// create preview run. Existing registries are preserved so nested calls share
// the same synthetic record universe.
func WithPreviewSyntheticRecordRegistry(ctx context.Context) context.Context {
	if PreviewSyntheticRecordRegistryFromCtx(ctx) != nil {
		return ctx
	}
	return context.WithValue(ctx, contextKeyPreviewSyntheticRecords, &PreviewSyntheticRecordRegistry{
		records: map[string]PreviewSyntheticRecord{},
	})
}

// WithPreviewSiblingMaxDefaultedFieldRegistry attaches an empty provenance
// registry for no-DML create preview sibling-max defaults.
func WithPreviewSiblingMaxDefaultedFieldRegistry(ctx context.Context) context.Context {
	if PreviewSiblingMaxDefaultedFieldRegistryFromCtx(ctx) != nil {
		return ctx
	}
	return context.WithValue(ctx, contextKeyPreviewSiblingMaxFields, &PreviewSiblingMaxDefaultedFieldRegistry{
		fields: map[string][]string{},
	})
}

// WithPreviewSiblingMaxDefaultedFieldToken identifies the preview insert whose
// sibling-max provenance may be registered by records preview preparation.
func WithPreviewSiblingMaxDefaultedFieldToken(ctx context.Context, token string) context.Context {
	return context.WithValue(ctx, contextKeyPreviewSiblingMaxToken, strings.TrimSpace(token))
}

// PreviewSyntheticRecordRegistryFromCtx returns the active preview registry, if
// any. Callers must not mutate returned internals directly.
func PreviewSyntheticRecordRegistryFromCtx(ctx context.Context) *PreviewSyntheticRecordRegistry {
	registry, _ := ctx.Value(contextKeyPreviewSyntheticRecords).(*PreviewSyntheticRecordRegistry)
	return registry
}

// PreviewSiblingMaxDefaultedFieldRegistryFromCtx returns the active
// sibling-max provenance registry, if any.
func PreviewSiblingMaxDefaultedFieldRegistryFromCtx(ctx context.Context) *PreviewSiblingMaxDefaultedFieldRegistry {
	registry, _ := ctx.Value(contextKeyPreviewSiblingMaxFields).(*PreviewSiblingMaxDefaultedFieldRegistry)
	return registry
}

// RegisterPreviewSyntheticRecord records a synthetic preview ID for downstream
// Flow output-reference wiring. Fields are copied so later Flow mutations
// cannot alter the registered preview image.
func RegisterPreviewSyntheticRecord(ctx context.Context, id string, objectName string, fields map[string]interface{}) {
	registry := PreviewSyntheticRecordRegistryFromCtx(ctx)
	if registry == nil || id == "" {
		return
	}
	if registry.records == nil {
		registry.records = map[string]PreviewSyntheticRecord{}
	}
	registry.records[id] = PreviewSyntheticRecord{
		ObjectName: objectName,
		Fields:     clonePreviewSyntheticFields(fields),
	}
}

// PreviewSyntheticRecordFromCtx returns a copied synthetic preview record by ID.
func PreviewSyntheticRecordFromCtx(ctx context.Context, id string) (PreviewSyntheticRecord, bool) {
	registry := PreviewSyntheticRecordRegistryFromCtx(ctx)
	if registry == nil || registry.records == nil || id == "" {
		return PreviewSyntheticRecord{}, false
	}
	record, ok := registry.records[id]
	if !ok {
		return PreviewSyntheticRecord{}, false
	}
	record.Fields = clonePreviewSyntheticFields(record.Fields)
	return record, true
}

// PreviewSyntheticRecordForObject returns a copied synthetic preview record
// when the ID and object both match.
func PreviewSyntheticRecordForObject(ctx context.Context, objectName string, id string) (PreviewSyntheticRecord, bool) {
	record, ok := PreviewSyntheticRecordFromCtx(ctx, id)
	if !ok {
		return PreviewSyntheticRecord{}, false
	}
	if !strings.EqualFold(strings.TrimSpace(record.ObjectName), strings.TrimSpace(objectName)) {
		return PreviewSyntheticRecord{}, false
	}
	return record, true
}

// PreviewSyntheticRecordsForObject returns copied synthetic preview records for
// one object. Callers use this to make request-local simulated writes visible
// to generic validators such as uniqueness checks while still performing no DB
// writes.
func PreviewSyntheticRecordsForObject(ctx context.Context, objectName string) []PreviewSyntheticRecord {
	registry := PreviewSyntheticRecordRegistryFromCtx(ctx)
	if registry == nil || registry.records == nil || objectName == "" {
		return nil
	}
	out := make([]PreviewSyntheticRecord, 0)
	for _, record := range registry.records {
		if !strings.EqualFold(strings.TrimSpace(record.ObjectName), strings.TrimSpace(objectName)) {
			continue
		}
		record.Fields = clonePreviewSyntheticFields(record.Fields)
		out = append(out, record)
	}
	return out
}

// RegisterPreviewSiblingMaxDefaultedFields records sibling-max provenance for
// the current preview insert token. Empty lists are intentionally ignored: the
// absence of a token entry means "no fields were sibling-max defaulted."
func RegisterPreviewSiblingMaxDefaultedFields(ctx context.Context, fields []string) {
	registry := PreviewSiblingMaxDefaultedFieldRegistryFromCtx(ctx)
	token, _ := ctx.Value(contextKeyPreviewSiblingMaxToken).(string)
	token = strings.TrimSpace(token)
	if registry == nil || token == "" || len(fields) == 0 {
		return
	}
	if registry.fields == nil {
		registry.fields = map[string][]string{}
	}
	registry.fields[token] = clonePreviewStringSlice(fields)
}

// ConsumePreviewSiblingMaxDefaultedFields returns and removes the provenance
// list associated with token.
func ConsumePreviewSiblingMaxDefaultedFields(ctx context.Context, token string) []string {
	registry := PreviewSiblingMaxDefaultedFieldRegistryFromCtx(ctx)
	token = strings.TrimSpace(token)
	if registry == nil || registry.fields == nil || token == "" {
		return nil
	}
	fields := registry.fields[token]
	delete(registry.fields, token)
	return clonePreviewStringSlice(fields)
}

func clonePreviewSyntheticFields(fields map[string]interface{}) map[string]interface{} {
	if fields == nil {
		return map[string]interface{}{}
	}
	out := make(map[string]interface{}, len(fields))
	for k, v := range fields {
		out[k] = v
	}
	return out
}

func clonePreviewStringSlice(in []string) []string {
	if len(in) == 0 {
		return nil
	}
	out := make([]string, len(in))
	copy(out, in)
	return out
}
