package exec

import (
	"encoding/json"
)

const (
	workflowScopeIDPattern  = `^[a-z0-9][a-z0-9_]{2,127}$`
	workflowNonblankPattern = `[^\t-\r\x20\x85\xA0\p{Z}]`
)

// workflowPlanInputSchema is the complete direct Plan authoring lifecycle
// exposed through one model-visible Exec action. Team-linked Plans use the
// same direct update path as unlinked Plans; no approval branch is exposed.
func workflowPlanInputSchema() json.RawMessage {
	branch := workflowSchemaBranch
	positiveRevision := map[string]any{"type": "integer", "minimum": 1}
	nonblankText := map[string]any{"type": "string", "minLength": 1, "pattern": workflowNonblankPattern}
	planID := nonblankText
	teamDescription := "Team scope. On create or update, link a reusable Plan to this Team and publish under the current Team Leader. Omit for unlinked workspace Plans."
	teamID := map[string]any{
		"type":        "string",
		"minLength":   1,
		"description": teamDescription,
	}
	limit := map[string]any{"type": "integer", "minimum": 1, "maximum": 100}
	propertiesWithPlan := func(properties map[string]any) map[string]any {
		out := make(map[string]any, len(properties)+1)
		out["planId"] = planID
		for key, value := range properties {
			out[key] = value
		}
		return out
	}
	branches := []any{
		branch("create", map[string]any{
			"teamId":        teamID,
			"key":           nonblankText,
			"displayName":   nonblankText,
			"descriptionMd": map[string]any{"type": "string"},
			"source":        map[string]any{"type": "string"},
		}, "key", "displayName", "source"),
		branch("list", map[string]any{
			"teamId":          teamID,
			"includeArchived": map[string]any{"type": "boolean"},
			"limit":           limit,
			"cursor":          map[string]any{"type": "string"},
		}),
		branch("show", propertiesWithPlan(map[string]any{
			"section": map[string]any{"type": "string", "enum": []string{"compact", "source"}},
			"path":    map[string]any{"type": "string", "maxLength": 512},
			"cursor":  map[string]any{"type": "string", "maxLength": 2048},
			"limit":   map[string]any{"type": "integer", "minimum": 1, "maximum": workflowRunMaxPayloadChunkBytes},
		}), "planId"),
		branch("version", map[string]any{
			"planId": planID, "versionId": nonblankText,
			"section": map[string]any{"type": "string", "enum": []string{"compact", "source", "layout"}},
			"path":    map[string]any{"type": "string", "maxLength": 512},
			"cursor":  map[string]any{"type": "string", "maxLength": 2048},
			"limit":   map[string]any{"type": "integer", "minimum": 1, "maximum": workflowRunMaxPayloadChunkBytes},
		}, "planId", "versionId"),
		branch("validate", map[string]any{"source": map[string]any{"type": "string"}}, "source"),
		branch("update", propertiesWithPlan(map[string]any{
			"teamId":               teamID,
			"expectedRevision":     positiveRevision,
			"expectedPlanRevision": positiveRevision,
			"draftRevision":        positiveRevision,
			"makeDefault":          map[string]any{"type": "boolean"},
			"source":               map[string]any{"type": "string"},
			"layout":               map[string]any{"type": "object", "additionalProperties": true},
		}), "planId", "expectedRevision", "source"),
		branch("publish", propertiesWithPlan(map[string]any{
			"expectedRevision": positiveRevision,
			"makeDefault":      map[string]any{"type": "boolean"},
		}), "planId", "expectedRevision"),
		branch("archive", propertiesWithPlan(map[string]any{
			"expectedRevision": positiveRevision,
		}), "planId", "expectedRevision"),
	}
	return mustWorkflowSchema(map[string]any{"oneOf": branches})
}

// workflowTriggerInputSchema mirrors the server Agent CLI contract while
// keeping Exec as the single owner of the model-visible action. Every branch is
// closed, so identity/provenance and fields belonging to another action cannot
// be smuggled through model JSON.
func workflowTriggerInputSchema() json.RawMessage {
	object := map[string]any{"type": "object", "additionalProperties": true}
	schedule := closedWorkflowObject(map[string]any{
		"mode":           map[string]any{"type": "string", "enum": []string{"cron", "interval", "absolute", "relative"}},
		"cronExpression": map[string]any{"type": "string"}, "timezone": map[string]any{"type": "string"},
		"intervalSeconds": map[string]any{"type": "integer", "minimum": 1}, "anchorAt": map[string]any{"type": "string"},
		"scheduledAt": map[string]any{"type": "string"}, "relativeSeconds": map[string]any{"type": "integer", "minimum": 1},
		"inputs": object,
	}, "mode")
	source := closedWorkflowObject(map[string]any{"type": map[string]any{"const": "schedule"}, "schedule": schedule}, "type", "schedule")
	branch := workflowSchemaBranch
	id := map[string]any{"type": "string", "minLength": 1}
	requiredID := map[string]any{"type": "string", "minLength": 1, "pattern": workflowNonblankPattern}
	revision := map[string]any{"type": "integer", "minimum": 1}
	limit := map[string]any{"type": "integer", "minimum": 1, "maximum": 100}
	revisionSpec := map[string]any{"planVersionId": requiredID, "source": source, "inputPolicy": closedWorkflowObject(map[string]any{"defaults": object, "constants": object})}
	withSpec := func(extra map[string]any) map[string]any {
		out := map[string]any{}
		for k, v := range revisionSpec {
			out[k] = v
		}
		for k, v := range extra {
			out[k] = v
		}
		return out
	}
	return mustWorkflowSchema(map[string]any{"oneOf": []any{
		branch("create", withSpec(map[string]any{"key": requiredID, "displayName": requiredID, "descriptionMd": map[string]any{"type": "string"}, "planId": requiredID, "enabled": map[string]any{"type": "boolean"}}), "key", "displayName", "planId", "planVersionId", "source", "enabled"),
		branch("list", map[string]any{"planId": id, "state": map[string]any{"type": "string"}, "includeArchived": map[string]any{"type": "boolean"}, "limit": limit, "cursor": map[string]any{"type": "string"}}),
		branch("show", map[string]any{"triggerId": requiredID}, "triggerId"),
		branch("validate", withSpec(map[string]any{"planId": requiredID}), "planId", "planVersionId", "source"),
		branch("update", withSpec(map[string]any{"triggerId": requiredID, "expectedRevision": revision}), "triggerId", "expectedRevision", "planVersionId", "source"),
		branch("enable", map[string]any{"triggerId": requiredID, "revision": revision, "expectedRevision": revision}, "triggerId", "revision", "expectedRevision"),
		branch("disable", map[string]any{"triggerId": requiredID, "expectedRevision": revision}, "triggerId", "expectedRevision"),
		branch("archive", map[string]any{"triggerId": requiredID, "expectedRevision": revision}, "triggerId", "expectedRevision"),
	}})
}

func workflowRunInputSchema() json.RawMessage {
	branch := workflowSchemaBranch
	id := map[string]any{"type": "string", "minLength": 1}
	requiredID := map[string]any{"type": "string", "minLength": 1, "pattern": workflowNonblankPattern}
	limit := map[string]any{"type": "integer", "minimum": 1, "maximum": 100}
	return mustWorkflowSchema(map[string]any{"oneOf": []any{
		branch("start", map[string]any{"planId": requiredID, "planVersionId": id, "teamId": id, "input": map[string]any{"type": "object"}}, "planId", "input"),
		branch("list", map[string]any{"planId": id, "status": map[string]any{"type": "string", "enum": []string{"queued", "running", "waiting", "succeeded", "failed", "canceled"}}, "limit": limit, "cursor": map[string]any{"type": "string"}}),
		branch("show", map[string]any{
			"runId":   requiredID,
			"section": map[string]any{"type": "string", "enum": []string{"compact", "input", "output"}},
			"path":    map[string]any{"type": "string", "maxLength": 512},
			"cursor":  map[string]any{"type": "string", "maxLength": 2048},
			"limit":   map[string]any{"type": "integer", "minimum": 1, "maximum": workflowRunMaxPayloadChunkBytes},
		}, "runId"),
		branch("logs", map[string]any{
			"runId": requiredID, "tail": map[string]any{"type": "integer", "minimum": 1, "maximum": 100},
			"limit": limit, "cursor": map[string]any{"type": "string", "maxLength": 2048}, "follow": map[string]any{"type": "boolean"},
			"section": map[string]any{"type": "string", "enum": []string{"summary", "details"}}, "path": map[string]any{"type": "string", "maxLength": 512}, "sequence": map[string]any{"type": "integer", "minimum": 1},
		}, "runId"),
		branch("cancel", map[string]any{"runId": requiredID, "reason": map[string]any{"type": "string", "maxLength": 2048}}, "runId"),
	}})
}

func workflowStepInputSchema() json.RawMessage {
	branch := workflowSchemaBranch
	id := map[string]any{"type": "string", "minLength": 1}
	key := map[string]any{"type": "string", "minLength": 1, "maxLength": 128}
	limit := map[string]any{"type": "integer", "minimum": 1, "maximum": 100}
	scope := closedWorkflowObject(map[string]any{"kind": map[string]any{"type": "string", "enum": []string{"channel", "thread", "dm", "task"}}, "scopeId": map[string]any{"type": "string", "pattern": workflowScopeIDPattern}}, "kind", "scopeId")
	// A WorkItem responder is either a fixed single agent string or a selectOne
	// candidate pool with an optional additive strategy ("ordered" legacy default
	// | "random"). Every candidate must belong to the frozen allowedResponders;
	// the pool is deduplicated preserving first-occurrence order and must not
	// exceed workflowRunMaxAllowedResponders.
	responder := map[string]any{"oneOf": []any{
		map[string]any{"type": "string", "minLength": 1},
		closedWorkflowObject(map[string]any{
			"selectOne": map[string]any{"type": "array", "items": map[string]any{"type": "string", "minLength": 1}, "minItems": 1, "maxItems": workflowRunMaxAllowedResponders},
			"strategy":  map[string]any{"type": "string", "enum": []string{"ordered", "random"}},
		}, "selectOne"),
	}}
	item := closedWorkflowObject(map[string]any{
		"key": map[string]any{"type": "string", "pattern": workflowWorkItemKeyPattern}, "dependsOn": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
		"workScope": scope, "relatedScopes": map[string]any{"type": "array", "items": scope}, "responder": responder, "instruction": map[string]any{"type": "string", "minLength": 1},
	}, "key", "workScope", "relatedScopes", "responder", "instruction")
	return mustWorkflowSchema(map[string]any{"oneOf": []any{
		branch("list", map[string]any{"cursor": map[string]any{"type": "string"}, "limit": limit}),
		branch("get", map[string]any{
			"runId": id, "stepId": id,
			"section": map[string]any{"type": "string", "enum": []string{"compact", "input", "output_contract", "work_items_contract"}},
			"path":    map[string]any{"type": "string", "maxLength": 512},
			"cursor":  map[string]any{"type": "string", "maxLength": 2048},
			"limit":   map[string]any{"type": "integer", "minimum": 1, "maximum": workflowRunMaxPayloadChunkBytes},
		}, "runId", "stepId"),
		branch("complete", map[string]any{"runId": id, "stepId": id, "output": map[string]any{"type": "object"}, "workScope": scope, "workItems": map[string]any{"type": "array", "items": item, "maxItems": workflowRunMaxWorkItems}, "idempotencyKey": key}, "runId", "stepId", "idempotencyKey"),
		branch("fail", map[string]any{"runId": id, "stepId": id, "code": map[string]any{"type": "string", "minLength": 1}, "message": map[string]any{"type": "string"}, "idempotencyKey": key}, "runId", "stepId", "code", "idempotencyKey"),
		branch("pause", map[string]any{"runId": id, "stepId": id, "idempotencyKey": key}, "runId", "stepId", "idempotencyKey"),
		branch("park", map[string]any{"runId": id, "stepId": id, "idempotencyKey": key}, "runId", "stepId"),
	}})
}

func workflowSchemaBranch(action string, properties map[string]any, required ...string) map[string]any {
	copyProperties := make(map[string]any, len(properties)+1)
	for key, value := range properties {
		copyProperties[key] = value
	}
	copyProperties["action"] = map[string]any{"const": action}
	return closedWorkflowObject(copyProperties, append([]string{"action"}, required...)...)
}

func closedWorkflowObject(properties map[string]any, required ...string) map[string]any {
	schema := map[string]any{
		"type":                 "object",
		"properties":           properties,
		"additionalProperties": false,
	}
	if len(required) > 0 {
		schema["required"] = required
	}
	return schema
}

func mustWorkflowSchema(schema map[string]any) json.RawMessage {
	raw, err := json.Marshal(schema)
	if err != nil {
		panic(err)
	}
	return raw
}
