package models

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	contractmodels "github.com/pingcap-inc/nexuscrm-contract/pkg/models"
)

// HydrateMetadata unmarshals MetadataJSON (from DB) into Metadata (typed struct
// for Go-side access) and stores the raw JSON in MetadataRaw (for lossless API
// responses).  Must be called after reading from the database.
func (v *FlowVersion) HydrateMetadata() {
	if v.MetadataJSON == "" {
		v.MetadataRaw = json.RawMessage("{}")
		return
	}
	v.MetadataRaw = json.RawMessage(v.MetadataJSON)
	_ = json.Unmarshal([]byte(v.MetadataJSON), &v.Metadata)
}

// SerializeMetadata converts metadata into MetadataJSON for DB storage.
// It prefers MetadataRaw (populated from inbound JSON requests) to avoid
// data loss from the typed struct roundtrip.  It also syncs both
// representations so Go-side code can access the typed Metadata struct.
// Must be called before writing to the database.
func (v *FlowVersion) SerializeMetadata() {
	if len(v.MetadataRaw) > 0 && string(v.MetadataRaw) != "{}" {
		v.MetadataJSON = string(v.MetadataRaw)
		_ = json.Unmarshal(v.MetadataRaw, &v.Metadata)
		return
	}
	data, err := json.Marshal(v.Metadata)
	if err != nil {
		return
	}
	v.MetadataJSON = string(data)
	v.MetadataRaw = json.RawMessage(v.MetadataJSON)
}

// ============================================================================
// Salesforce Flow API Types
// These types match Salesforce's wire-protocol for Flow APIs
// ============================================================================

// Flow status constants
const (
	FlowStatusActive = "Active"
	FlowStatusDraft  = "Draft"
)

// FlowDefinition represents a Salesforce FlowDefinition object
// Queried via Tooling API: SELECT ... FROM FlowDefinition
type FlowDefinition struct {
	Id                   string `json:"Id" db:"id"`
	DeveloperName        string `json:"DeveloperName" db:"developer_name"`
	MasterLabel          string `json:"MasterLabel" db:"master_label"`
	Description          string `json:"Description,omitempty" db:"description"`
	ProcessType          string `json:"ProcessType" db:"process_type"`                      // AutoLaunchedFlow, Workflow, CustomEvent, etc.
	TriggerType          string `json:"TriggerType,omitempty" db:"trigger_type"`            // RecordBeforeSave, RecordAfterSave, Scheduled, PlatformEvent
	TriggerObjectOrEvent string `json:"TriggerObjectOrEvent,omitempty" db:"trigger_object"` // Object API name for record-triggered flows
	ActiveVersionId      string `json:"ActiveVersionId,omitempty" db:"active_version_id"`   // Currently active version
	LatestVersionId      string `json:"LatestVersionId,omitempty" db:"latest_version_id"`   // Most recent version
	IsDeleted            bool   `json:"-" db:"is_deleted"`
	IsCustomized         bool   `json:"-" db:"is_customized"`
	OwnerPack            string `json:"-" db:"owner_pack"`
	CreatedDate          string `json:"CreatedDate,omitempty" db:"created_at"`
	LastModifiedDate     string `json:"LastModifiedDate,omitempty" db:"updated_at"`
	// TriggerOrder is a read-through Tooling projection from the active FlowVersion
	// metadata. It is NOT stored in the FlowDefinition DB row — the authoritative
	// value lives in Flow.Metadata.start.triggerOrder on the active version.
	// Populated at query time by the Tooling GET/LIST handlers via
	// triggerprojection.ResolveTriggerOrder; never written by any lifecycle path.
	TriggerOrder *int `json:"TriggerOrder,omitempty" db:"-"`
}

// FlowDefinitionPatch models the sparse Tooling PATCH surface for
// admin-authored definition edits. Internal reconcile state such as
// IsDeleted/IsCustomized is intentionally excluded.
type FlowDefinitionPatch struct {
	DeveloperName        *string `json:"DeveloperName,omitempty"`
	MasterLabel          *string `json:"MasterLabel,omitempty"`
	Description          *string `json:"Description,omitempty"`
	ProcessType          *string `json:"ProcessType,omitempty"`
	TriggerType          *string `json:"TriggerType,omitempty"`
	TriggerObjectOrEvent *string `json:"TriggerObjectOrEvent,omitempty"`
	ActiveVersionId      *string `json:"ActiveVersionId,omitempty"`
	LatestVersionId      *string `json:"LatestVersionId,omitempty"`
}

func (p *FlowDefinitionPatch) Empty() bool {
	return p == nil ||
		(p.DeveloperName == nil &&
			p.MasterLabel == nil &&
			p.Description == nil &&
			p.ProcessType == nil &&
			p.TriggerType == nil &&
			p.TriggerObjectOrEvent == nil &&
			p.ActiveVersionId == nil &&
			p.LatestVersionId == nil)
}

// FlowVersion represents a specific version of a flow (Salesforce Flow object in Tooling API)
// Queried via Tooling API: SELECT ... FROM Flow WHERE DefinitionId = '...'
type FlowVersion struct {
	Id                      string          `json:"Id" db:"id"`
	DefinitionId            string          `json:"DefinitionId" db:"definition_id"`
	MasterLabel             string          `json:"MasterLabel,omitempty" db:"master_label"`
	VersionNumber           int             `json:"VersionNumber" db:"version_number"`
	Status                  string          `json:"Status" db:"status"` // Active, Draft, Obsolete, InvalidDraft
	Description             string          `json:"Description,omitempty" db:"description"`
	ProcessType             string          `json:"ProcessType" db:"process_type"`
	Source                  string          `json:"-" db:"source"`
	DefinitionIsCustomized  bool            `json:"-" db:"-"`
	DefinitionOwnerPack     string          `json:"-" db:"-"`
	DefinitionRuntimeLoaded bool            `json:"-" db:"-"`
	IsDeleted               bool            `json:"-" db:"is_deleted"`
	Metadata                FlowMetadata    `json:"-" db:"-"`        // Typed struct for Go-side access (executor, triggers)
	MetadataJSON            string          `json:"-" db:"metadata"` // Raw JSON string for DB storage
	MetadataRaw             json.RawMessage `json:"Metadata" db:"-"` // Raw JSON for lossless API response
	CreatedDate             string          `json:"CreatedDate,omitempty" db:"created_at"`
	LastModifiedDate        string          `json:"LastModifiedDate,omitempty" db:"updated_at"`
}

// FlowMetadata contains the flow canvas structure
type FlowMetadata struct {
	// ApiVersion is the Salesforce Flow API version the canvas was built
	// against. Salesforce writes this as a JSON number in live flow metadata
	// (e.g. 57, 63.0), but older NexusCRM fixtures committed before
	// Phase 3 used the string form ("63.0"). We accept either shape by
	// typing as interface{} — the engine does not branch on this field at
	// runtime (grep confirms: nothing reads FlowMetadata.ApiVersion), it
	// is preserved solely for roundtrip fidelity with Salesforce
	// Metadata. A stricter shape would fail HydrateMetadata silently
	// (it swallows unmarshal errors) and leave every flow with an empty
	// ApiVersion — which is how this bug hid in production until Phase 3
	// tests surfaced it.
	ApiVersion  interface{} `json:"apiVersion,omitempty"`
	Status      string      `json:"status,omitempty"`
	Label       string      `json:"label,omitempty"`
	Description string      `json:"description,omitempty"`
	// RunAtomically opts a flow into single-transaction execution: every
	// RecordCreate/RecordUpdate/RecordDelete the flow performs commits or
	// rolls back as one unit. Required for invocable actions that must
	// preserve cross-object atomicity (e.g. Lead Convert). Default false
	// preserves the existing non-atomic semantics. Current platform support
	// is intentionally narrower than "all record-triggered flows":
	// RecordBeforeSave roots are supported, as is the one strict
	// RecordAfterSave shape with a single AsyncAfterCommit entry and no
	// immediate/top-level, scheduled, or connector-sourced entry. All other
	// RecordAfterSave / RecordBeforeDelete / RecordAfterDelete atomic trigger
	// shapes are rejected by shared validation used from Tooling, setup/apply,
	// and tenant-overlay paths. Atomic flows may not
	// contain Wait elements or emailAlert action calls — either would hold
	// the database transaction open across external I/O (pause or SMTP
	// send). Runtime enforcement also rejects unsupported shapes for legacy
	// rows and blocks Wait/emailAlert for both self-declared atomic flows
	// and subflows running under an inherited atomic parent boundary.
	RunAtomically         bool                       `json:"runAtomically,omitempty"`
	Start                 *FlowStart                 `json:"start,omitempty"`
	StartElementReference string                     `json:"startElementReference,omitempty"`
	Variables             []FlowVariable             `json:"variables,omitempty"`
	Decisions             []FlowDecision             `json:"decisions,omitempty"`
	Assignments           []FlowAssignment           `json:"assignments,omitempty"`
	RecordCreates         []FlowRecordCreate         `json:"recordCreates,omitempty"`
	RecordUpdates         []FlowRecordUpdate         `json:"recordUpdates,omitempty"`
	RecordDeletes         []FlowRecordDelete         `json:"recordDeletes,omitempty"`
	RecordLookups         []FlowRecordLookup         `json:"recordLookups,omitempty"`
	AtomicParentPrelocks  []FlowAtomicParentPrelock  `json:"atomicParentPrelocks,omitempty"`
	AtomicHistoryPrelocks []FlowAtomicHistoryPrelock `json:"atomicHistoryPrelocks,omitempty"`
	ActionCalls           []FlowActionCall           `json:"actionCalls,omitempty"`
	Subflows              []FlowSubflow              `json:"subflows,omitempty"`
	Loops                 []FlowLoop                 `json:"loops,omitempty"`
	Screens               []FlowScreen               `json:"screens,omitempty"`
	CustomErrors          []FlowCustomError          `json:"customErrors,omitempty"`
	Waits                 []FlowWait                 `json:"waits,omitempty"`
	// Resource types — preserved for roundtrip fidelity with Salesforce Metadata
	Formulas      []FlowFormula      `json:"formulas,omitempty"`
	Constants     []FlowConstant     `json:"constants,omitempty"`
	TextTemplates []FlowTextTemplate `json:"textTemplates,omitempty"`
	Choices       []FlowChoice       `json:"choices,omitempty"`
	Stages        []FlowStage        `json:"stages,omitempty"`
}

// FlowAtomicParentPrelock declares parent rows that an atomic flow can discover
// from its input-bound context before the first business mutation.
type FlowAtomicParentPrelock struct {
	Name                 string             `json:"name"`
	ParentObject         string             `json:"parentObject"`
	RecordIDs            []interface{}      `json:"recordIds,omitempty"`
	SourceObject         string             `json:"sourceObject,omitempty"`
	ParentReferenceField string             `json:"parentReferenceField,omitempty"`
	FilterLogic          string             `json:"filterLogic,omitempty"`
	Filters              []FlowRecordFilter `json:"filters,omitempty"`
}

// FlowWait represents a Wait element that pauses the flow until one of the
// configured WaitEvents fires. Salesforce uses Wait elements for time-delayed
// actions — e.g. "send reminder 30 days before contract expiration".
//
// Semantics (matching Salesforce Metadata API):
//   - At execution time each WaitEvent is evaluated. If exactly one is ready
//     now (non-time-based and its conditions pass, or time-based with its
//     target already elapsed), the flow takes that event's connector.
//   - Otherwise (all pending events are time-based and none have elapsed)
//     the interview is persisted with InterviewStatus=Paused and
//     PausedUntil set to the earliest WaitEvent's target time. The
//     scheduler resumes it when PausedUntil <= NOW().
//   - If no event can ever fire, DefaultConnector is taken; on malformed
//     input the FaultConnector is taken.
type FlowWait struct {
	Name             string          `json:"name"`
	Label            string          `json:"label,omitempty"`
	Description      string          `json:"description,omitempty"`
	LocationX        int             `json:"locationX,omitempty"`
	LocationY        int             `json:"locationY,omitempty"`
	WaitEvents       []FlowWaitEvent `json:"waitEvents,omitempty"`
	DefaultConnector *FlowConnector  `json:"defaultConnector,omitempty"`
	FaultConnector   *FlowConnector  `json:"faultConnector,omitempty"`
}

// FlowWaitEvent is a single resume condition in a Wait element. EventType
// distinguishes time-based waits (e.g. "AlarmEvent", "DateRefAlarmEvent")
// — which trigger on wall-clock time — from non-time-based waits whose
// conditions must already evaluate true at execution time.
type FlowWaitEvent struct {
	Name            string                `json:"name,omitempty"`
	Label           string                `json:"label,omitempty"`
	ConditionLogic  string                `json:"conditionLogic,omitempty"`
	Conditions      []FlowCondition       `json:"conditions,omitempty"`
	Connector       *FlowConnector        `json:"connector,omitempty"`
	EventType       string                `json:"eventType,omitempty"`
	InputParameters []FlowActionParameter `json:"inputParameters,omitempty"`
}

// FlowStart defines flow entry point and trigger conditions
type FlowStart struct {
	LocationX         int                  `json:"locationX"`
	LocationY         int                  `json:"locationY"`
	Connector         *FlowConnector       `json:"connector,omitempty"`
	Object            string               `json:"object,omitempty"`            // For record-triggered flows
	TriggerType       string               `json:"triggerType,omitempty"`       // RecordBeforeSave, RecordAfterSave, etc.
	RecordTriggerType string               `json:"recordTriggerType,omitempty"` // Create, Update, CreateAndUpdate, Delete
	FilterLogic       string               `json:"filterLogic,omitempty"`
	FilterFormula     string               `json:"filterFormula,omitempty"`
	Filters           []FlowRecordFilter   `json:"filters,omitempty"`
	ScheduledPaths    []FlowScheduledPath  `json:"scheduledPaths,omitempty"`
	Schedule          *FlowSchedule        `json:"schedule,omitempty"`
	ConnectorSource   *FlowConnectorSource `json:"connectorSource,omitempty"`
}

// FlowConnectorSource declares that a scheduled Flow sources its input sets by
// actively pulling a connector directory (an external list endpoint) via the
// connector callout engine, instead of a local SObject query. Application-neutral:
// a callout-profile reference plus JSONPaths into the response; no object/app
// semantics are encoded here.
type FlowConnectorSource struct {
	OwnerPack   string `json:"ownerPack"`   // pack owning the ConnectorCalloutProfile
	ProfileName string `json:"profileName"` // ConnectorCalloutProfile name
	ObjectKey   string `json:"objectKey"`   // logical key into the profile's objectPathMap (request path)
	ItemsPath   string `json:"itemsPath"`   // JSONPath to the array of items in the response body
	ItemIDPath  string `json:"itemIdPath"`  // JSONPath to each item's stable unique id (relative to the item)
}

// FlowConnector links flow elements
type FlowConnector struct {
	TargetReference string `json:"targetReference"`
}

// FlowVariable stores flow state
type FlowVariable struct {
	Name         string      `json:"name"`
	DataType     string      `json:"dataType"` // String, Number, Boolean, Date, DateTime, Currency, SObject, etc.
	IsCollection bool        `json:"isCollection,omitempty"`
	IsInput      bool        `json:"isInput,omitempty"`
	IsOutput     bool        `json:"isOutput,omitempty"`
	Value        interface{} `json:"value,omitempty"`
	ObjectType   string      `json:"objectType,omitempty"` // For SObject variables
}

// FlowDecision represents a decision element with multiple outcomes
type FlowDecision struct {
	Name                  string             `json:"name"`
	Label                 string             `json:"label,omitempty"`
	Description           string             `json:"description,omitempty"`
	LocationX             int                `json:"locationX"`
	LocationY             int                `json:"locationY"`
	Rules                 []FlowDecisionRule `json:"rules,omitempty"`
	DefaultConnector      *FlowConnector     `json:"defaultConnector,omitempty"`
	DefaultConnectorLabel string             `json:"defaultConnectorLabel,omitempty"`
}

type FlowDecisionRule struct {
	Name           string          `json:"name"`
	Label          string          `json:"label,omitempty"`
	ConditionLogic string          `json:"conditionLogic,omitempty"` // and, or, custom
	Conditions     []FlowCondition `json:"conditions,omitempty"`
	Connector      *FlowConnector  `json:"connector,omitempty"`
}

type FlowCondition struct {
	LeftValueReference string      `json:"leftValueReference"`
	Operator           string      `json:"operator"` // EqualTo, NotEqualTo, GreaterThan, Contains, etc.
	RightValue         interface{} `json:"rightValue,omitempty"`
}

// FlowAssignment modifies flow variables
type FlowAssignment struct {
	Name        string               `json:"name"`
	Label       string               `json:"label,omitempty"`
	Description string               `json:"description,omitempty"`
	LocationX   int                  `json:"locationX"`
	LocationY   int                  `json:"locationY"`
	Items       []FlowAssignmentItem `json:"assignmentItems,omitempty"`
	Connector   *FlowConnector       `json:"connector,omitempty"`
}

type FlowAssignmentItem struct {
	AssignToReference string      `json:"assignToReference"`
	Operator          string      `json:"operator"` // Assign, Add, Subtract, etc.
	Value             interface{} `json:"value,omitempty"`
}

// FlowRecordCreate creates a record
type FlowRecordCreate struct {
	Name             string                `json:"name"`
	Label            string                `json:"label,omitempty"`
	LocationX        int                   `json:"locationX"`
	LocationY        int                   `json:"locationY"`
	Object           string                `json:"object"`
	InputAssignments []FlowInputAssignment `json:"inputAssignments,omitempty"`
	// UpsertByExternalIDField changes this element from insert-only to an
	// atomic create-or-adopt write keyed by the named field. The field value is
	// read from the fully assembled record image after mappings, assignments,
	// and defaults have run. Runtime persistence validates the field and owns
	// the unique-race rollback/replay boundary.
	UpsertByExternalIDField string `json:"upsertByExternalIdField,omitempty"`
	// UpsertAdoptionGuardFields prevents the external identity from adopting a
	// row already owned by another non-blank claimant. Guard conflicts alone
	// may follow AdoptionConflictConnector; all other errors remain on the
	// ordinary fail-closed fault path.
	UpsertAdoptionGuardFields []string       `json:"upsertAdoptionGuardFields,omitempty"`
	AdoptionConflictConnector *FlowConnector `json:"adoptionConflictConnector,omitempty"`
	StoreOutputAutomatically  bool           `json:"storeOutputAutomatically,omitempty"`
	AssignRecordIdToReference string         `json:"assignRecordIdToReference,omitempty"`
	// FieldMapping applies rows from the _System_ObjectFieldMapping registry
	// to the new record before InputAssignments run, letting admins drive
	// object-to-object transforms (e.g. Lead→Contact on Lead Convert) as
	// data. InputAssignments always override mapped values, so a Flow
	// author can opt in to the registry yet still pin specific fields.
	FieldMapping          *FlowFieldMappingRef          `json:"fieldMapping,omitempty"`
	ConnectorFieldMapping *FlowConnectorFieldMappingRef `json:"connectorFieldMapping,omitempty"`
	Connector             *FlowConnector                `json:"connector,omitempty"`
	FaultConnector        *FlowConnector                `json:"faultConnector,omitempty"`
}

// FlowFieldMappingRef references a group of rows in _System_ObjectFieldMapping
// keyed by (SourceObject, TargetObject, ActionType). The Flow executor
// resolves SourceReference from the interview context to locate the source
// SObject at runtime.
type FlowFieldMappingRef struct {
	SourceObject    string `json:"sourceObject"`
	SourceReference string `json:"sourceReference"`
	TargetObject    string `json:"targetObject,omitempty"`
	TargetReference string `json:"targetReference,omitempty"`
	ActionType      string `json:"actionType"`
	MergeMode       string `json:"mergeMode,omitempty"`
}

// FlowConnectorFieldMappingRef applies pack-owned, direction-aware connector
// field mappings to a record mutation. Values use the same Flow typed-value
// shapes as action input parameters, so actionType/sourceRecord/extraUpdates
// can be literals or element references.
type FlowConnectorFieldMappingRef struct {
	Source            interface{} `json:"source,omitempty"`
	SourceObject      interface{} `json:"sourceObject"`
	TargetObject      interface{} `json:"targetObject,omitempty"`
	ActionType        interface{} `json:"actionType"`
	Direction         interface{} `json:"direction"`
	SourceRecord      interface{} `json:"sourceRecord"`
	ExtraUpdates      interface{} `json:"extraUpdates,omitempty"`
	ApplyExtraUpdates interface{} `json:"applyExtraUpdates,omitempty"`
	// SkipBlankTargetFields omits raw blank overwrite values for the named
	// targets while preserving blank-clear behavior for every other mapping.
	SkipBlankTargetFields interface{} `json:"skipBlankTargetFields,omitempty"`
}

// FlowRecordUpdate updates records
type FlowRecordUpdate struct {
	Name                  string                `json:"name"`
	Label                 string                `json:"label,omitempty"`
	LocationX             int                   `json:"locationX"`
	LocationY             int                   `json:"locationY"`
	Object                string                `json:"object,omitempty"`
	InputReference        string                `json:"inputReference,omitempty"` // Reference to SObject variable
	InputAssignments      []FlowInputAssignment `json:"inputAssignments,omitempty"`
	FieldMapping          *FlowFieldMappingRef  `json:"fieldMapping,omitempty"`
	FilterLogic           string                `json:"filterLogic,omitempty"`
	Filters               []FlowRecordFilter    `json:"filters,omitempty"`
	RecheckFiltersAtWrite bool                  `json:"recheckFiltersAtWrite,omitempty"`
	Connector             *FlowConnector        `json:"connector,omitempty"`
	FaultConnector        *FlowConnector        `json:"faultConnector,omitempty"`
}

// FlowRecordDelete deletes records
type FlowRecordDelete struct {
	Name           string             `json:"name"`
	Label          string             `json:"label,omitempty"`
	LocationX      int                `json:"locationX"`
	LocationY      int                `json:"locationY"`
	Object         string             `json:"object,omitempty"`
	InputReference string             `json:"inputReference,omitempty"`
	FilterLogic    string             `json:"filterLogic,omitempty"`
	Filters        []FlowRecordFilter `json:"filters,omitempty"`
	Connector      *FlowConnector     `json:"connector,omitempty"`
	FaultConnector *FlowConnector     `json:"faultConnector,omitempty"`
}

// FlowRecordLookup queries records. QueriedFields is the exact record output
// closure: relationship values must be requested explicitly (for example
// Owner.Name); selecting OwnerId alone does not include OwnerId_Name.
type FlowRecordLookup struct {
	Name                     string                 `json:"name"`
	Label                    string                 `json:"label,omitempty"`
	Description              string                 `json:"description,omitempty"`
	LocationX                int                    `json:"locationX"`
	LocationY                int                    `json:"locationY"`
	Object                   string                 `json:"object"`
	FilterLogic              string                 `json:"filterLogic,omitempty"`
	Filters                  []FlowRecordFilter     `json:"filters,omitempty"`
	QueriedFields            []string               `json:"queriedFields,omitempty"`
	OutputAssignments        []FlowOutputAssignment `json:"outputAssignments,omitempty"`
	OutputReference          string                 `json:"outputReference,omitempty"`
	StoreOutputAutomatically bool                   `json:"storeOutputAutomatically,omitempty"`
	GetFirstRecordOnly       bool                   `json:"getFirstRecordOnly,omitempty"`
	SortOrder                string                 `json:"sortOrder,omitempty"`
	SortField                string                 `json:"sortField,omitempty"`
	// CoalesceInFlight is an L0 execution hint, authored by metadata, that lets
	// identical concurrent lookups share only the in-flight DB query result.
	// It is not a TTL cache and does not make the lookup result a source of truth.
	CoalesceInFlight bool           `json:"coalesceInFlight,omitempty"`
	Connector        *FlowConnector `json:"connector,omitempty"`
	FaultConnector   *FlowConnector `json:"faultConnector,omitempty"`
}

// FlowProcessMetadataValue is a Salesforce Metadata API annotation carried
// by Process Builder-generated flow elements. The engine uses it to detect
// the "implicit-Id" pattern on RecordUpdate/RecordDelete filters.
type FlowProcessMetadataValue struct {
	Name  string      `json:"name"`
	Value interface{} `json:"value,omitempty"`
}

type FlowRecordFilter struct {
	Field                 string                     `json:"field"`
	Operator              string                     `json:"operator"`
	Value                 interface{}                `json:"value,omitempty"`
	ProcessMetadataValues []FlowProcessMetadataValue `json:"processMetadataValues,omitempty"`
}

type FlowInputAssignment struct {
	Field string      `json:"field"`
	Value interface{} `json:"value,omitempty"`
}

type FlowOutputAssignment struct {
	AssignToReference string `json:"assignToReference"`
	Field             string `json:"field"`
}

// FlowActionCall invokes an action (Apex, Flow, Quick Action, etc.)
type FlowActionCall struct {
	Name             string                `json:"name"`
	Label            string                `json:"label,omitempty"`
	LocationX        int                   `json:"locationX"`
	LocationY        int                   `json:"locationY"`
	ActionName       string                `json:"actionName"`
	ActionType       string                `json:"actionType"` // apex, flow, quickAction, emailAlert, etc.
	InputParameters  []FlowActionParameter `json:"inputParameters,omitempty"`
	OutputParameters []FlowActionParameter `json:"outputParameters,omitempty"`
	Connector        *FlowConnector        `json:"connector,omitempty"`
	FaultConnector   *FlowConnector        `json:"faultConnector,omitempty"`
}

type FlowActionParameter struct {
	Name  string      `json:"name"`
	Value interface{} `json:"value,omitempty"`
}

// FlowSubflow calls another flow
type FlowSubflow struct {
	Name              string              `json:"name"`
	Label             string              `json:"label,omitempty"`
	LocationX         int                 `json:"locationX"`
	LocationY         int                 `json:"locationY"`
	FlowName          string              `json:"flowName"`
	InputAssignments  []FlowSubflowInput  `json:"inputAssignments,omitempty"`
	OutputAssignments []FlowSubflowOutput `json:"outputAssignments,omitempty"`
	Connector         *FlowConnector      `json:"connector,omitempty"`
	FaultConnector    *FlowConnector      `json:"faultConnector,omitempty"`
}

type FlowSubflowInput struct {
	Name  string      `json:"name"`
	Value interface{} `json:"value,omitempty"`
}

type FlowSubflowOutput struct {
	AssignToReference string `json:"assignToReference"`
	Name              string `json:"name"`
}

// FlowLoop iterates over a collection
type FlowLoop struct {
	Name                       string         `json:"name"`
	Label                      string         `json:"label,omitempty"`
	LocationX                  int            `json:"locationX"`
	LocationY                  int            `json:"locationY"`
	CollectionReference        string         `json:"collectionReference"`                  // Variable containing the collection
	IterationOrder             string         `json:"iterationOrder,omitempty"`             // Asc, Desc
	AssignNextValueToReference string         `json:"assignNextValueToReference,omitempty"` // Variable to store current item
	NextValueConnector         *FlowConnector `json:"nextValueConnector,omitempty"`         // Loop body
	NoMoreValuesConnector      *FlowConnector `json:"noMoreValuesConnector,omitempty"`      // Exit path
}

// FlowScreen represents a screen flow element (for user interaction)
type FlowScreen struct {
	Name      string            `json:"name"`
	Label     string            `json:"label,omitempty"`
	LocationX int               `json:"locationX"`
	LocationY int               `json:"locationY"`
	Fields    []FlowScreenField `json:"fields,omitempty"`
	Connector *FlowConnector    `json:"connector,omitempty"`
}

type FlowScreenField struct {
	Name         string      `json:"name"`
	FieldType    string      `json:"fieldType"`
	DataType     string      `json:"dataType,omitempty"`
	IsRequired   bool        `json:"isRequired,omitempty"`
	DefaultValue interface{} `json:"defaultValue,omitempty"`
}

// FlowSchedule defines scheduled flow execution
type FlowSchedule struct {
	Frequency      string `json:"frequency"` // Once, Daily, Weekly
	StartDate      string `json:"startDate,omitempty"`
	StartTime      string `json:"startTime,omitempty"`
	TimezoneSidKey string `json:"timezoneSidKey,omitempty"` // e.g. "UTC", "America/New_York"
}

type FlowScheduledPath struct {
	Name         string         `json:"name"`
	Label        string         `json:"label,omitempty"`
	Connector    *FlowConnector `json:"connector,omitempty"`
	PathType     string         `json:"pathType,omitempty"`   // AsyncAfterCommit
	TimeSource   string         `json:"timeSource,omitempty"` // RecordField
	OffsetNumber int            `json:"offsetNumber,omitempty"`
	OffsetUnit   string         `json:"offsetUnit,omitempty"` // Hours, Days
}

// FlowCustomError represents a custom error element that blocks record saves
type FlowCustomError struct {
	Name                string                   `json:"name"`
	Label               string                   `json:"label,omitempty"`
	Description         string                   `json:"description,omitempty"`
	LocationX           int                      `json:"locationX"`
	LocationY           int                      `json:"locationY"`
	CustomErrorMessages []FlowCustomErrorMessage `json:"customErrorMessages,omitempty"`
	Connector           *FlowConnector           `json:"connector,omitempty"`
}

// FlowCustomErrorMessage defines the error message details
//
// StatusCode carries the Salesforce-native error code the dispatcher should
// surface in FlowExecutionResult.Errors[].StatusCode (e.g. "ALREADY_CONVERTED",
// "MALFORMED_ID", "FIELD_CUSTOM_VALIDATION_EXCEPTION"). Absent → the dispatcher
// falls back to the generic "FLOW_ERROR" sentinel. Lets flows raise
// SF-API-contract-compatible errors without a Go code change.
type FlowCustomErrorMessage struct {
	ErrorMessage   string `json:"errorMessage"`
	StatusCode     string `json:"statusCode,omitempty"`
	FieldSelection string `json:"fieldSelection,omitempty"`
	IsFieldError   bool   `json:"isFieldError,omitempty"`
}

// ============================================================================
// Flow Resource Types (for roundtrip fidelity with Salesforce Metadata)
// ============================================================================

// FlowFormula represents a formula resource in a flow
type FlowFormula struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	DataType    string `json:"dataType"`
	Expression  string `json:"expression"`
	Scale       int    `json:"scale,omitempty"`
}

// FlowConstant represents a constant resource in a flow
type FlowConstant struct {
	Name        string      `json:"name"`
	Description string      `json:"description,omitempty"`
	DataType    string      `json:"dataType"`
	Value       interface{} `json:"value,omitempty"`
}

// FlowTextTemplate represents a text template resource in a flow
type FlowTextTemplate struct {
	Name                string `json:"name"`
	Description         string `json:"description,omitempty"`
	Text                string `json:"text"`
	IsViewedAsPlainText bool   `json:"isViewedAsPlainText,omitempty"`
}

// FlowChoice represents a choice resource in a flow
type FlowChoice struct {
	Name        string      `json:"name"`
	Description string      `json:"description,omitempty"`
	ChoiceText  string      `json:"choiceText"`
	DataType    string      `json:"dataType"`
	Value       interface{} `json:"value,omitempty"`
}

// FlowStage represents a stage in a flow
type FlowStage struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Label       string `json:"label"`
	IsActive    bool   `json:"isActive,omitempty"`
	StageOrder  int    `json:"stageOrder,omitempty"`
}

// ============================================================================
// Flow Interview (Runtime Instance)
// ============================================================================

// FlowInterview represents a running/paused flow instance
// Queried via sObject API: SELECT ... FROM FlowInterview
type FlowInterview struct {
	Id              string     `json:"Id" db:"id"`
	FlowVersionId   string     `json:"FlowVersionId" db:"flow_version_id"`
	InterviewLabel  string     `json:"InterviewLabel,omitempty" db:"interview_label"`
	InterviewStatus string     `json:"InterviewStatus" db:"interview_status"` // Paused, Running, Error, Finished
	CurrentElement  string     `json:"CurrentElement,omitempty" db:"current_element"`
	OwnerId         string     `json:"OwnerId" db:"owner_id"`
	CreatedDate     time.Time  `json:"CreatedDate" db:"created_at"`
	PausedDate      *time.Time `json:"PausedDate,omitempty" db:"paused_date"`
	// PausedUntil is set when the interview is paused inside a Wait element.
	// Distinct from PausedDate (which records *when* it was paused):
	// PausedUntil records *when to wake up*. The scheduler polls
	//   SELECT Id FROM FlowInterview WHERE InterviewStatus='Paused' AND PausedUntil <= NOW()
	// (backed by the composite index idx_flow_interview_paused_scan) and
	// calls ResumeInterview on each match.
	PausedUntil *time.Time `json:"PausedUntil,omitempty" db:"paused_until"`
	// ContextData stores flow variable values during execution
	ContextData     map[string]interface{} `json:"-" db:"-"`
	ContextDataJSON string                 `json:"-" db:"context_data"`
	// IsTransient marks an interview that exists only in memory for the
	// duration of a single synchronous run. The flow executor uses this
	// for record-triggered flows that do not pause: their interview row
	// has no audit value (it would be inserted as Running then immediately
	// updated to Finished with no intervening state), and the two extra
	// DB round-trips dominate save latency when a record has many flows
	// (e.g. Lead has ~24 before-save flows → ~160s on remote TiDB Cloud).
	// Salesforce itself defaults to not storing finished record-triggered
	// interviews — see Process Automation Settings → "Store Flow Interviews".
	// This field is in-memory only and never serialized or persisted.
	IsTransient bool `json:"-" db:"-"`
}

// FlowInterview status constants
const (
	FlowInterviewStatusRunning  = "Running"
	FlowInterviewStatusPaused   = "Paused"
	FlowInterviewStatusFinished = "Finished"
	FlowInterviewStatusError    = "Error"
)

// FlowInstanceProgress provides progress info for approval workflows
type FlowInstanceProgress struct {
	InstanceId     string `json:"instance_id"`
	FlowName       string `json:"flow_name"`
	CurrentElement string `json:"current_element"`
	Status         string `json:"status"`
	CompletedSteps int    `json:"completed_steps"`
	TotalSteps     int    `json:"total_steps"`
}

// ============================================================================
// Actions API Types
// ============================================================================

// InvocableFlow represents a flow listed via Actions API
// GET /services/data/v63.0/actions/custom/flow
type InvocableFlow struct {
	Label       string            `json:"label"`
	Name        string            `json:"name"`
	Type        string            `json:"type"` // FLOW
	Url         string            `json:"url"`
	Description string            `json:"description,omitempty"`
	Inputs      []FlowInputOutput `json:"inputs,omitempty"`
	Outputs     []FlowInputOutput `json:"outputs,omitempty"`
}

// FlowInputOutput describes flow input/output parameters
type FlowInputOutput struct {
	Name        string `json:"name"`
	Label       string `json:"label,omitempty"`
	DataType    string `json:"dataType"`
	Required    bool   `json:"required,omitempty"`
	Description string `json:"description,omitempty"`
}

// FlowExecutionRequest represents input for flow execution
// POST /services/data/v63.0/actions/custom/flow/:name
type FlowExecutionRequest struct {
	Inputs []map[string]interface{} `json:"inputs"`
}

// FlowExecutionResult represents the result of flow execution
type FlowExecutionResult = contractmodels.FlowExecutionResult

// FlowExecutionError represents an error during flow execution
type FlowExecutionError = contractmodels.FlowExecutionError

// ============================================================================
// Tooling API Query Response Types
// ============================================================================

// ToolingQueryResponse wraps Tooling API query results
type ToolingQueryResponse[T any] struct {
	Size         int    `json:"size"`
	TotalSize    int    `json:"totalSize"`
	Done         bool   `json:"done"`
	QueryLocator string `json:"queryLocator,omitempty"`
	Records      []T    `json:"records"`
}

// ValidateConnectorSource enforces the connector-directory source field contract.
// No-op when no connector source is declared. When declared, fails closed on
// (a) co-presence of local-query Start fields (Object/Filters/FilterLogic/
// FilterFormula) — external directory items carry no NexusCRM schema — and
// (b) any of the 5 required connector-source fields being blank.
// The runtime builder calls this; authoring/apply callers are a tracked follow-up.
func ValidateConnectorSource(start *FlowStart) error {
	if start == nil || start.ConnectorSource == nil {
		return nil
	}
	if strings.TrimSpace(start.Object) != "" ||
		len(start.Filters) > 0 ||
		strings.TrimSpace(start.FilterLogic) != "" ||
		strings.TrimSpace(start.FilterFormula) != "" {
		return fmt.Errorf("connector directory source cannot be combined with Start.Object/Filters/FilterLogic/FilterFormula; external items carry no NexusCRM schema, so filter inside the flow body instead")
	}
	src := start.ConnectorSource
	switch {
	case strings.TrimSpace(src.OwnerPack) == "":
		return fmt.Errorf("connector directory source: OwnerPack is required")
	case strings.TrimSpace(src.ProfileName) == "":
		return fmt.Errorf("connector directory source: ProfileName is required")
	case strings.TrimSpace(src.ObjectKey) == "":
		return fmt.Errorf("connector directory source: ObjectKey is required")
	case strings.TrimSpace(src.ItemsPath) == "":
		return fmt.Errorf("connector directory source: ItemsPath is required")
	case strings.TrimSpace(src.ItemIDPath) == "":
		return fmt.Errorf("connector directory source: ItemIDPath is required")
	}
	return nil
}
