package standardaction

import (
	"context"

	"github.com/nexuscrm/automation"
	cruntimeports "github.com/nexuscrm/db/pkg/runtimeports"
	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	seedrows "github.com/pingcap-inc/nexuscrm-contract/pkg/seedrows"
)

// Action is the engine-side view of one _System_StandardAction registry row.
// Wire-specific handlers may project it, but action liveness and permission
// metadata are owned here so every dialect resolves the same registry state.
type Action struct {
	ID                       string
	Name                     string
	FlowDeveloperName        string
	PreviewFlowDeveloperName string
	RequiredPermissions      []string
	IsAtomic                 bool
	IsActive                 bool
	IsDeleted                bool
}

const InputDefaultTarget = "$Input"

// Neutral standard-action rejection reasons. The engine is app-blind: it names
// the reason in neutral snake_case and the wire dialect renders the product's
// error-code spelling (e.g. a dialect maps invalid_request to its own uppercase
// error code). These values are response-only (never persisted), so renaming
// them is a pure wire-projection change owned by the dialect.
const (
	RejectInvalidRequest       = "invalid_request"
	RejectRequiredFieldMissing = "required_field_missing"
	RejectInvalidField         = "invalid_field"
	RejectInsufficientPerms    = "insufficient_permissions"
	RejectUnknownException     = "unknown_exception"
)

// Internal diagnostic kinds identify the validator stage without copying
// caller-controlled values into operational logs. They are deliberately not a
// wire contract; external callers continue to receive only the stable reject
// code and disclosure-safe message owned by their dialect.
const (
	diagnosticCatalogInputSchemaEmpty = "catalog_input_schema_empty"
	diagnosticInputDefaultCondition   = "input_default_condition"
	diagnosticInputDefaultFormula     = "input_default_formula"
	diagnosticRequiredInput           = "required_input"
	diagnosticUnknownInput            = "unknown_input"
	diagnosticPicklistMetadata        = "picklist_metadata"
	diagnosticPicklistTargetField     = "picklist_target_field"
	diagnosticPicklistTag             = "picklist_tag"
	diagnosticGuardCondition          = "guard_condition"
	diagnosticGuardAuthorization      = "guard_authorization"
	diagnosticGuardDenied             = "guard_denied"
)

func (a *Action) IsInvokable() bool {
	return a != nil && a.IsActive && !a.IsDeleted
}

type ActionState int

const (
	ActionAbsent ActionState = iota
	ActionActive
	ActionDisabled
)

type ActionResolution struct {
	State  ActionState
	Action *Action
}

type standardActionBinding struct {
	action Action
}

type ListEntry struct {
	ID                       string
	Name                     string
	Label                    string
	Description              string
	Category                 string
	FlowDeveloperName        string
	IsAtomic                 bool
	RequiredPermissions      []string
	TargetObjects            []string
	RendererID               string
	VisibilityCondition      string
	PreviewFlowDeveloperName string
	HeaderFallbackEligible   bool
}

type SchemaField 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
	IsCollection              bool
	PrefillSourceFieldAPIName string
	TargetRequired            bool
	TargetDefaulted           bool
	Defaulted                 bool
	HasDefaultValue           bool
	DefaultValue              interface{}
}

type SchemaContract struct {
	Inputs  []SchemaField
	Outputs []SchemaField
}

type inputDefaultSummary struct {
	defaulted       bool
	hasDefaultValue bool
	defaultValue    interface{}
}

type PreparedInvocation struct {
	Action        *Action
	Context       context.Context
	Schema        []seedrows.ActionSchemaRow
	FieldDefaults []seedrows.ActionFieldDefaultRow
	Guards        []seedrows.ActionInputGuardRow
}

type InputReject struct {
	Code     string
	Message  string
	Internal bool

	diagnosticKind   string
	diagnosticTarget string
}

// DiagnosticKind returns a value-free validator-stage identifier for internal
// observability. It must never contain input values or tenant-authored text.
func (r *InputReject) DiagnosticKind() string {
	if r == nil {
		return ""
	}
	return r.diagnosticKind
}

// DiagnosticTarget identifies the value-free permission target that rejected
// an input guard. Catalog validation constrains this to either a permission API
// name or an object API name plus one closed CRUD operation. It deliberately
// excludes caller input values and tenant-authored error text.
func (r *InputReject) DiagnosticTarget() string {
	if r == nil {
		return ""
	}
	return r.diagnosticTarget
}

type OutputPreviewInput struct {
	RecordContextInput string
	FlowInputs         map[string]interface{}
}

type InputGuardVerdict struct {
	GatedInput          string
	RequiredPermission  string
	RequiredObject      string
	RequiredOperation   string
	Condition           string
	CallerHasPermission bool
}

type OutputPreviewResult = contract.StandardActionOutputPreview

type InvocationResult = contract.StandardActionInvocationResult

type CreatePreviewImage = contract.StandardActionCreatePreviewImage

type ChildPreview = contract.StandardActionChildPreview

type CreatePreviewResult = contract.StandardActionPreviewResult

type RequestOutcomeKind int

const (
	RequestOutcomeUnauthorized RequestOutcomeKind = iota
)

type RequestOutcome struct {
	Kind    RequestOutcomeKind
	Message string
}

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

type PermissionChecker interface {
	HasPermission(ctx context.Context, user *models.UserSession, permissionAPIName string) bool
	CheckObjectPermissionWithUser(ctx context.Context, objectAPIName string, operation string, user *models.UserSession) bool
}

type MetadataProvider interface {
	GetSchema(ctx context.Context, apiName string) *models.ObjectMetadata
}

type PreviewAccessChecker interface {
	CheckObjectPermissionWithUser(ctx context.Context, objectAPIName string, operation string, user *models.UserSession) bool
	CheckFieldVisibilityWithUser(ctx context.Context, objectAPIName, fieldAPIName string, user *models.UserSession) (bool, error)
}

type FlowRunner func(ctx context.Context, flowName string, inputs map[string]interface{}, user *models.UserSession) (*contract.FlowExecutionResult, error)

type CreatePreviewRunner func(ctx context.Context, flowName string, inputs map[string]interface{}, user *models.UserSession) (*automation.FlowRecordCreatePreviewResult, error)

type OutputPreviewRunner func(ctx context.Context, flowName string, inputs map[string]interface{}, user *models.UserSession) (*automation.FlowPreviewOutputsResult, error)

type CreatePreviewSupportChecker func(ctx context.Context, flowName string, user *models.UserSession) (bool, error)

type RuntimeDeps struct {
	Metadata               MetadataProvider
	PreviewAccess          PreviewAccessChecker
	Formulas               *formula.Engine
	Execute                FlowRunner
	CreatePreview          CreatePreviewRunner
	OutputPreview          OutputPreviewRunner
	CreatePreviewSupported CreatePreviewSupportChecker
	// StandardActions is the read-only port for the standard-action metadata
	// reads (schema / field-defaults / input-guards / internal-access-scopes /
	// field-mapping source lookup). Injected at the composition root so the
	// service depends on the contract port, not on internal/bootstrap.
	StandardActions cruntimeports.StandardActionReader
}
