package exec

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

	invopopjsonschema "github.com/invopop/jsonschema"
	orderedmap "github.com/wk8/go-ordered-map/v2"
)

type workflowObject []byte

func (o *workflowObject) UnmarshalJSON(raw []byte) error {
	raw = bytes.TrimSpace(raw)
	if len(raw) == 0 || raw[0] != '{' || !json.Valid(raw) {
		return fmt.Errorf("workflow value must be a JSON object")
	}
	*o = append((*o)[:0], raw...)
	return nil
}

func (o workflowObject) MarshalJSON() ([]byte, error) {
	if len(o) == 0 {
		return []byte(`{}`), nil
	}
	return append([]byte(nil), o...), nil
}

func (workflowObject) JSONSchema() *invopopjsonschema.Schema {
	return &invopopjsonschema.Schema{
		Type:                 "object",
		AdditionalProperties: invopopjsonschema.TrueSchema,
	}
}

type workflowValue []byte

func (v *workflowValue) UnmarshalJSON(raw []byte) error {
	raw = bytes.TrimSpace(raw)
	if len(raw) == 0 || !json.Valid(raw) {
		return fmt.Errorf("workflow value must be valid JSON")
	}
	*v = append((*v)[:0], raw...)
	return nil
}

func (v workflowValue) MarshalJSON() ([]byte, error) {
	if len(v) == 0 {
		return []byte(`null`), nil
	}
	return append([]byte(nil), v...), nil
}

func (workflowValue) JSONSchema() *invopopjsonschema.Schema {
	return invopopjsonschema.TrueSchema
}

type workflowOperationalJSON []byte

func (v *workflowOperationalJSON) UnmarshalJSON(raw []byte) error {
	raw = bytes.TrimSpace(raw)
	if len(raw) == 0 || !json.Valid(raw) {
		return fmt.Errorf("workflow operational value must be valid JSON")
	}
	*v = append((*v)[:0], raw...)
	return nil
}

func (v workflowOperationalJSON) MarshalJSON() ([]byte, error) {
	if len(v) == 0 {
		return []byte(`null`), nil
	}
	return append([]byte(nil), v...), nil
}

func (workflowOperationalJSON) JSONSchema() *invopopjsonschema.Schema {
	return invopopjsonschema.TrueSchema
}

type workflowNullableObject []byte

func (v *workflowNullableObject) UnmarshalJSON(raw []byte) error {
	raw = bytes.TrimSpace(raw)
	if bytes.Equal(raw, []byte("null")) {
		*v = append((*v)[:0], raw...)
		return nil
	}
	if len(raw) == 0 || raw[0] != '{' || !json.Valid(raw) {
		return fmt.Errorf("workflow nullable object must be null or a JSON object")
	}
	*v = append((*v)[:0], raw...)
	return nil
}

func (v workflowNullableObject) MarshalJSON() ([]byte, error) {
	if len(v) == 0 {
		return []byte(`null`), nil
	}
	return append([]byte(nil), v...), nil
}

func (workflowNullableObject) JSONSchema() *invopopjsonschema.Schema {
	return &invopopjsonschema.Schema{
		OneOf: []*invopopjsonschema.Schema{
			workflowObject{}.JSONSchema(),
			{Type: "null"},
		},
	}
}

type workflowPlanRequest struct {
	Action               string          `json:"action"`
	TeamID               string          `json:"teamId,omitempty"`
	Key                  string          `json:"key,omitempty"`
	DisplayName          string          `json:"displayName,omitempty"`
	DescriptionMD        string          `json:"descriptionMd,omitempty"`
	Source               string          `json:"source,omitempty"`
	Layout               *workflowObject `json:"layout,omitempty"`
	IncludeArchived      bool            `json:"includeArchived,omitempty"`
	Limit                int             `json:"limit,omitempty"`
	Cursor               string          `json:"cursor,omitempty"`
	Path                 string          `json:"path,omitempty"`
	Section              string          `json:"section,omitempty"`
	PlanID               string          `json:"planId,omitempty"`
	VersionID            string          `json:"versionId,omitempty"`
	ExpectedRevision     int64           `json:"expectedRevision,omitempty"`
	ExpectedPlanRevision int64           `json:"expectedPlanRevision,omitempty"`
	DraftRevision        int64           `json:"draftRevision,omitempty"`
	MakeDefault          bool            `json:"makeDefault,omitempty"`
	Version              int64           `json:"version,omitempty"`
}

func (workflowPlanRequest) JSONSchema() *invopopjsonschema.Schema {
	return mustTypedWorkflowSchema(workflowPlanInputSchema())
}

type workflowTriggerScheduleInput struct {
	Mode            string         `json:"mode"`
	CronExpression  string         `json:"cronExpression,omitempty"`
	Timezone        string         `json:"timezone,omitempty"`
	IntervalSeconds *int64         `json:"intervalSeconds,omitempty"`
	AnchorAt        *time.Time     `json:"anchorAt,omitempty"`
	ScheduledAt     *time.Time     `json:"scheduledAt,omitempty"`
	RelativeSeconds *int64         `json:"relativeSeconds,omitempty"`
	Inputs          workflowObject `json:"inputs"`
}

type workflowTriggerSourceInput struct {
	Type     string                        `json:"type"`
	Schedule *workflowTriggerScheduleInput `json:"schedule,omitempty"`
}

type workflowTriggerInputPolicyInput struct {
	Defaults  workflowObject `json:"defaults"`
	Constants workflowObject `json:"constants"`
}

type workflowTriggerRequest struct {
	Action           string                           `json:"action"`
	Key              string                           `json:"key,omitempty"`
	DisplayName      string                           `json:"displayName,omitempty"`
	DescriptionMD    string                           `json:"descriptionMd,omitempty"`
	PlanID           string                           `json:"planId,omitempty"`
	PlanVersionID    string                           `json:"planVersionId,omitempty"`
	Source           *workflowTriggerSourceInput      `json:"source,omitempty"`
	InputPolicy      *workflowTriggerInputPolicyInput `json:"inputPolicy,omitempty"`
	Enabled          *bool                            `json:"enabled,omitempty"`
	State            string                           `json:"state,omitempty"`
	IncludeArchived  bool                             `json:"includeArchived,omitempty"`
	Limit            int                              `json:"limit,omitempty"`
	Cursor           string                           `json:"cursor,omitempty"`
	TriggerID        string                           `json:"triggerId,omitempty"`
	ExpectedRevision int64                            `json:"expectedRevision,omitempty"`
	Revision         int64                            `json:"revision,omitempty"`
}

func (workflowTriggerRequest) JSONSchema() *invopopjsonschema.Schema {
	return mustTypedWorkflowSchema(workflowTriggerInputSchema())
}

type workflowRunRequest struct {
	Action        string `json:"action"`
	PlanID        string `json:"planId,omitempty"`
	PlanVersionID string `json:"planVersionId,omitempty"`
	// TeamID optionally names the Team to freeze for an unlinked/dynamic Run.
	// The server authorizes the invoking Agent's membership; omitting it means
	// an ordinary non-Team Run (no automatic inheritance). A Team-linked Plan
	// gets its Team from the approved, activated Plan/Team binding instead.
	TeamID   string          `json:"teamId,omitempty"`
	Input    *workflowObject `json:"input,omitempty"`
	Status   string          `json:"status,omitempty"`
	Limit    int             `json:"limit,omitempty"`
	Cursor   string          `json:"cursor,omitempty"`
	Section  string          `json:"section,omitempty"`
	Path     string          `json:"path,omitempty"`
	Sequence int64           `json:"sequence,omitempty"`
	RunID    string          `json:"runId,omitempty"`
	Tail     int             `json:"tail,omitempty"`
	Follow   bool            `json:"follow,omitempty"`
	Reason   string          `json:"reason,omitempty"`
}

type workflowStepCommand struct {
	Action         string                     `json:"action"`
	RunID          string                     `json:"runId,omitempty"`
	StepID         string                     `json:"stepId,omitempty"`
	Section        string                     `json:"section,omitempty"`
	Path           string                     `json:"path,omitempty"`
	Cursor         string                     `json:"cursor,omitempty"`
	Limit          int                        `json:"limit,omitempty"`
	Output         *workflowObject            `json:"output,omitempty"`
	WorkScope      *workflowMessageScopeInput `json:"workScope,omitempty"`
	WorkItems      *[]workflowWorkItemInput   `json:"workItems,omitempty"`
	IdempotencyKey string                     `json:"idempotencyKey,omitempty"`
	Code           string                     `json:"code,omitempty"`
	Message        string                     `json:"message,omitempty"`
}

type workflowMessageScopeInput struct {
	Kind    string `json:"kind"`
	ScopeID string `json:"scopeId"`
}

// workflowWorkItemInput is the closed, first-class callback payload consumed
// by a compiled fan_out node. A pointer-to-slice on workflowRunRequest keeps
// an intentional empty batch distinguishable from an omitted workItems field.
type workflowWorkItemInput struct {
	Key           string                      `json:"key"`
	DependsOn     []string                    `json:"dependsOn,omitempty"`
	WorkScope     workflowMessageScopeInput   `json:"workScope"`
	RelatedScopes []workflowMessageScopeInput `json:"relatedScopes"`
	// Responder is either a fixed agent string or an ordered selectOne candidate
	// pool object {selectOne: [...]}. Runtime normalize enforces that every
	// candidate belongs to the frozen allowedResponders and that the pool stays
	// within the safe upper bound.
	Responder   workflowWorkItemResponderInput `json:"responder"`
	Instruction string                         `json:"instruction"`
}

// workflowWorkItemResponderInput is the concrete WorkItem responder union: a
// fixed single agent string or an ordered selectOne candidate pool
// {selectOne: [...]}. It preserves the raw JSON so the OpenAPI catalog and the
// callback decoder share one shape without pinning the responder to a single
// agent. Runtime normalize enforces allowedResponders membership and the safe
// upper bound.
type workflowWorkItemResponderInput []byte

func (v *workflowWorkItemResponderInput) UnmarshalJSON(raw []byte) error {
	raw = bytes.TrimSpace(raw)
	if len(raw) == 0 || !json.Valid(raw) {
		return fmt.Errorf("workflow WorkItem responder must be a fixed agent string or a selectOne candidate pool")
	}
	*v = append((*v)[:0], raw...)
	return nil
}

func (v workflowWorkItemResponderInput) MarshalJSON() ([]byte, error) {
	if len(v) == 0 {
		return []byte(`""`), nil
	}
	return append([]byte(nil), v...), nil
}

func (workflowWorkItemResponderInput) JSONSchema() *invopopjsonschema.Schema {
	selectOneItems := &invopopjsonschema.Schema{Type: "string", MinLength: uint64Pointer(1)}
	minItems := uint64(1)
	maxItems := uint64(workflowRunMaxAllowedResponders)
	selectOneProperties := orderedmap.New[string, *invopopjsonschema.Schema]()
	selectOneProperties.Set("selectOne", &invopopjsonschema.Schema{
		Type:     "array",
		Items:    selectOneItems,
		MinItems: &minItems,
		MaxItems: &maxItems,
	})
	ordered := "ordered"
	random := "random"
	selectOneProperties.Set("strategy", &invopopjsonschema.Schema{
		Type:    "string",
		Enum:    []any{ordered, random},
		Default: ordered,
	})
	return &invopopjsonschema.Schema{
		OneOf: []*invopopjsonschema.Schema{
			{Type: "string", MinLength: uint64Pointer(1)},
			{
				Type:                 "object",
				Properties:           selectOneProperties,
				Required:             []string{"selectOne"},
				AdditionalProperties: invopopjsonschema.FalseSchema,
			},
		},
	}
}

func (v workflowWorkItemResponderInput) value() any {
	if len(v) == 0 {
		return ""
	}
	var fixed string
	if err := json.Unmarshal(v, &fixed); err == nil {
		return fixed
	}
	var object map[string]any
	if err := json.Unmarshal(v, &object); err == nil {
		return object
	}
	return string(v)
}

func (workflowRunRequest) JSONSchema() *invopopjsonschema.Schema {
	return mustTypedWorkflowSchema(workflowRunInputSchema())
}

func (workflowStepCommand) JSONSchema() *invopopjsonschema.Schema {
	return mustTypedWorkflowSchema(workflowStepInputSchema())
}
