package ones

import (
	"slices"

	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	"github.com/tidbcloud/AgentTeam/internal/extension/spec"
)

// Action name constants for the ONES catalog.
const (
	ActionGetAuthInfo      = "get-auth-info"
	ActionListProjects     = "list-projects"
	ActionCreateProject    = "create-project"
	ActionListIssueTypes   = "list-issue-types"
	ActionListTaskStatuses = "list-task-statuses"
	ActionCreateTask       = "create-task"
	ActionUpdateTask       = "update-task"
	ActionGetTask          = "get-task"
	ActionGetTasks         = "get-tasks"
	ActionDeleteTask       = "delete-task"
	ActionFilterTasks      = "filter-tasks"
	ActionListSprints      = "list-sprints"
	ActionCreateSprint     = "create-sprint"
	ActionBindTaskPage     = "bind-task-page"
	ActionUnbindTaskPage   = "unbind-task-page"
	ActionListSpaces       = "list-spaces"
	ActionCreateSpace      = "create-space"
	ActionGetSpace         = "get-space"
	ActionUpdateSpace      = "update-space"
	ActionDeleteSpace      = "delete-space"
	ActionListPages        = "list-pages"
	ActionGetPage          = "get-page"
	ActionCreatePage       = "create-page"
	ActionUpdatePage       = "update-page"
	ActionDeletePage       = "delete-page"
)

// Definition returns the Agent-visible ONES catalog, assembled from the
// per-resource action groups below.
func Definition() registry.Definition {
	return registry.Definition{
		Domain:      Domain,
		Version:     1,
		Name:        "ONES",
		Description: "Manage ONES projects, work items, sprints, and the Wiki knowledge base on the connected deployment.",
		Actions: slices.Concat(
			authActions(),
			projectActions(),
			taskActions(),
			sprintActions(),
			wikiLinkActions(),
			spaceActions(),
			pageActions(),
		),
	}
}

func readSafety() spec.Safety {
	return spec.Safety{Class: "read", Idempotent: true, Destructive: false}
}

func writeSafety() spec.Safety {
	return spec.Safety{Class: "mutation", Idempotent: false, Destructive: false}
}

func destructiveSafety() spec.Safety {
	return spec.Safety{Class: "destructive", Idempotent: false, Destructive: true}
}

func authActions() []registry.DefinitionAction {
	return []registry.DefinitionAction{{
		Name:        ActionGetAuthInfo,
		Summary:     "Get the connected ONES login info",
		Description: "Get the current user, teams, and organization of the connected credentials. Use teams[].uuid to scope every other action.",
		Safety:      readSafety(),
		InputSchema: noInputSchema(),
		OutputSchema: objectSchema(map[string]any{
			"user": objectSchema(map[string]any{
				"uuid":          uuid8Schema("The user uuid."),
				"email":         nullableStringSchema("The user email."),
				"name":          stringSchema("The user display name."),
				"name_pinyin":   nullableStringSchema("The user name pinyin."),
				"avatar":        nullableStringSchema("The user avatar URL."),
				"phone":         nullableStringSchema("The user phone."),
				"status":        nullableIntegerSchema("The user status."),
				"create_time":   nullableIntegerSchema("The user creation timestamp."),
				"channel":       nullableStringSchema("The user push channel."),
				"license_types": nullableIntegerArraySchema("The licensed product types."),
			}, []string{"uuid", "name"}),
			"teams": rawArraySchema("teams", "The teams the user belongs to."),
			"org":   nullableRawObjectSchema("The organization object, or null when absent."),
		}, []string{"user"}),
	}}
}

func projectActions() []registry.DefinitionAction {
	return []registry.DefinitionAction{
		{
			Name:        ActionListProjects,
			Summary:     "List ONES projects",
			Description: "List the projects the current user can see (my_project); with ids, fetch those project uuids through the info route.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"ids": map[string]any{
					"type":        "string",
					"pattern":     "^[A-Za-z0-9]{16}(,[A-Za-z0-9]{16})*$",
					"description": "Optional comma-separated project uuid filter for the info route.",
				},
			}, []string{"team_uuid"}),
			OutputSchema: objectSchema(map[string]any{
				"projects":            rawArraySchema("projects", "The matching project objects."),
				"archive_projects":    rawArraySchema("archive_projects", "Archived projects (my_project route)."),
				"server_update_stamp": nullableIntegerSchema("The team update stamp (my_project route)."),
				"errors":              rawArraySchema("errors", "Per-id lookup failures on the info route."),
			}, nil),
		},
		{
			Name:        ActionCreateProject,
			Summary:     "Create an ONES project",
			Description: "Create a project in the team. The project uuid is client-generated (16 characters); assign defaults to the creator.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"project": objectSchema(map[string]any{
					"uuid":   uuid16Schema("Client-generated project uuid (user uuid + 8 random characters)."),
					"assign": uuid8Schema("The owner user uuid."),
					"name":   boundedStringSchema("The project name.", 128),
				}, []string{"uuid", "assign", "name"}),
				"template_id": nullableStringSchema("Optional project template, default project-t1."),
				"members":     uuid8ArraySchema("Optional initial member user uuids.", 0),
			}, []string{"team_uuid", "project"}),
			OutputSchema: objectSchema(map[string]any{
				"project":             rawObjectSchema("The created project object."),
				"server_update_stamp": nullableIntegerSchema("The team update stamp."),
			}, nil),
		},
		{
			Name:        ActionListIssueTypes,
			Summary:     "List ONES work item types",
			Description: "List the work item types configured in the team; a type uuid is required to create work items.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
			}, []string{"team_uuid"}),
			OutputSchema: rawObjectSchema("The ONES issue type response."),
		},
		{
			Name:        ActionListTaskStatuses,
			Summary:     "List ONES work item statuses",
			Description: "List the work item statuses configured in the team.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
			}, []string{"team_uuid"}),
			OutputSchema: rawObjectSchema("The ONES task status response."),
		},
	}
}

// taskCreateItemSchema types the official add2 work item contract: uuid,
// assign, project/type scoping, summary, and priority are required; the rest
// stays nullable or passthrough exactly as documented.
func taskCreateItemSchema() map[string]any {
	return objectSchema(map[string]any{
		"uuid":            uuid16Schema("Work item uuid (creator uuid + 8 random characters)."),
		"assign":          uuid8Schema("The assignee user uuid."),
		"project_uuid":    uuid16Schema("The project uuid."),
		"issue_type_uuid": uuid8Schema("The work item type uuid."),
		"summary":         boundedStringSchema("The work item title.", 1024),
		"priority":        uuid8Schema("The priority field option uuid."),
		"parent_uuid":     nullableUUID16Schema("The parent work item uuid."),
		"sprint_uuid":     nullableUUID8Schema("The sprint uuid."),
		"desc":            nullableBoundedStringSchema("The plain-text description.", 65536),
		"desc_rich":       nullableBoundedStringSchema("The rich-text description.", 65536),
		"deadline":        nullableIntegerSchema("The deadline timestamp."),
		"product_uuids":   uuid8ArraySchema("Related product uuids.", 0),
		"field_values":    looseObjectArraySchema("Custom field values (field_uuid/type/value).", 0),
	}, []string{"uuid", "assign", "project_uuid", "issue_type_uuid", "summary", "priority"})
}

// taskUpdateItemSchema types the update3 patch: the uuid keys the patch,
// every other documented field stays optional.
func taskUpdateItemSchema() map[string]any {
	return objectSchema(map[string]any{
		"uuid":            uuid16Schema("The work item uuid to update."),
		"assign":          uuid8Schema("The assignee user uuid."),
		"project_uuid":    uuid16Schema("The project uuid."),
		"issue_type_uuid": uuid8Schema("The work item type uuid."),
		"summary":         boundedStringSchema("The work item title.", 1024),
		"priority":        uuid8Schema("The priority field option uuid."),
		"parent_uuid":     nullableUUID16Schema("The parent work item uuid."),
		"sprint_uuid":     nullableUUID8Schema("The sprint uuid."),
		"desc":            nullableBoundedStringSchema("The plain-text description.", 65536),
		"desc_rich":       nullableBoundedStringSchema("The rich-text description.", 65536),
		"deadline":        nullableIntegerSchema("The deadline timestamp."),
		"field_values":    looseObjectArraySchema("Custom field values (field_uuid/type/value).", 0),
	}, []string{"uuid"})
}

func taskActions() []registry.DefinitionAction {
	return []registry.DefinitionAction{
		{
			Name:        ActionCreateTask,
			Summary:     "Create ONES work items",
			Description: "Create one or more work items via the batch add2 endpoint. Partial failures are reported in bad_tasks.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"tasks": map[string]any{
					"type":        "array",
					"minItems":    1,
					"items":       taskCreateItemSchema(),
					"description": "The work items to create.",
				},
			}, []string{"team_uuid", "tasks"}),
			OutputSchema: objectSchema(map[string]any{
				"tasks":     rawArraySchema("tasks", "The created work item objects."),
				"bad_tasks": rawArraySchema("bad_tasks", "The rejected work items with code/desc."),
			}, nil),
		},
		{
			Name:        ActionUpdateTask,
			Summary:     "Update ONES work items",
			Description: "Update one or more work items via the batch update3 endpoint. Partial failures are reported in bad_tasks.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"tasks": map[string]any{
					"type":        "array",
					"minItems":    1,
					"items":       taskUpdateItemSchema(),
					"description": "The work item patches keyed by uuid.",
				},
			}, []string{"team_uuid", "tasks"}),
			OutputSchema: objectSchema(map[string]any{
				"tasks":     rawArraySchema("tasks", "The updated work item objects."),
				"bad_tasks": rawArraySchema("bad_tasks", "The rejected work items with code/desc."),
			}, nil),
		},
		{
			Name:        ActionGetTask,
			Summary:     "Get one ONES work item",
			Description: "Get a work item by uuid or serial number.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"task":      uuid16OrNumberSchema("The work item uuid or serial number."),
			}, []string{"team_uuid", "task"}),
			OutputSchema: rawObjectSchema("The ONES work item object."),
		},
		{
			Name:        ActionGetTasks,
			Summary:     "Get ONES work items in bulk",
			Description: "Fetch work item details for a list of uuids; per-id failures are reported in errors.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"ids":       uuid16ArraySchema("The work item uuids.", 1),
			}, []string{"team_uuid", "ids"}),
			OutputSchema: objectSchema(map[string]any{
				"tasks":  rawArraySchema("tasks", "The fetched work item objects."),
				"count":  nullableIntegerSchema("The fetched count."),
				"errors": rawArraySchema("errors", "Per-id failures with uuid/code/desc."),
			}, nil),
		},
		{
			Name:        ActionDeleteTask,
			Summary:     "Delete an ONES work item",
			Description: "Permanently delete a work item by uuid.",
			Safety:      destructiveSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"task_uuid": uuid16Schema("The work item uuid."),
			}, []string{"team_uuid", "task_uuid"}),
			OutputSchema: rawObjectSchema("The ONES deletion acknowledgement."),
		},
		{
			Name:        ActionFilterTasks,
			Summary:     "Search ONES work items",
			Description: "Run the raw filter query contract (must/should/in DSL over field_values) to discover work items before reading or updating them.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":            teamUUIDSchema,
				"query":                rawObjectSchema("The ONES filter query DSL (must/should/in)."),
				"sort":                 looseObjectArraySchema("Sort clauses like {\"field_values.field009\":{\"order\":\"desc\"}}.", 0),
				"group_by":             nullableStringSchema("Optional group-by field."),
				"include_subtasks":     nullableBooleanSchema("Whether to include subtasks."),
				"with_boards":          nullableBooleanSchema("Whether board groups are requested."),
				"boards":               looseObjectArraySchema("Board scoping.", 0),
				"include_status_uuid":  nullableBooleanSchema("Embed status uuids in entries."),
				"include_issue_type":   nullableBooleanSchema("Embed issue type uuids in entries."),
				"include_project_uuid": nullableBooleanSchema("Embed project uuids in entries."),
				"is_show_derive":       nullableBooleanSchema("Whether derived items are shown."),
			}, []string{"team_uuid", "query"}),
			OutputSchema: objectSchema(map[string]any{
				"groups": rawArraySchema("groups", "The grouped result entries."),
				"total":  nullableIntegerSchema("The total matching count."),
				"count":  nullableIntegerSchema("The returned count."),
			}, nil),
		},
	}
}

func sprintActions() []registry.DefinitionAction {
	return []registry.DefinitionAction{
		{
			Name:        ActionListSprints,
			Summary:     "List ONES sprints",
			Description: "List the sprints of one project.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":    teamUUIDSchema,
				"project_uuid": uuid16Schema("The project uuid."),
			}, []string{"team_uuid", "project_uuid"}),
			OutputSchema: rawObjectSchema("The ONES sprint list response."),
		},
		{
			Name:        ActionCreateSprint,
			Summary:     "Create ONES sprints",
			Description: "Create one or more sprints in a project via the batch endpoint. Partial failures are reported in bad_sprints.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":    teamUUIDSchema,
				"project_uuid": uuid16Schema("The project uuid."),
				"sprints": map[string]any{
					"type":     "array",
					"minItems": 1,
					"items": objectSchema(map[string]any{
						"title":         boundedStringSchema("The sprint title.", 32),
						"assign":        uuid8Schema("The owner user uuid."),
						"start_time":    nullableIntegerSchema("The sprint start timestamp."),
						"end_time":      nullableIntegerSchema("The sprint end timestamp."),
						"period":        nullableStringSchema("The sprint period, for example 1w."),
						"goal":          nullableStringSchema("The sprint goal."),
						"is_open_gantt": nullableBooleanSchema("Whether the sprint opens the gantt view."),
						"statuses":      looseObjectArraySchema("Sprint stage entries.", 0),
						"fields":        looseObjectArraySchema("Sprint field values.", 0),
					}, []string{"title"}),
					"description": "The sprints to create.",
				},
			}, []string{"team_uuid", "project_uuid", "sprints"}),
			OutputSchema: objectSchema(map[string]any{
				"sprints":             rawArraySchema("sprints", "The created sprint objects."),
				"bad_sprints":         rawArraySchema("bad_sprints", "The rejected sprints."),
				"server_update_stamp": nullableIntegerSchema("The update stamp."),
			}, nil),
		},
	}
}

func wikiLinkActions() []registry.DefinitionAction {
	return []registry.DefinitionAction{
		{
			Name:        ActionBindTaskPage,
			Summary:     "Link Wiki pages to a work item",
			Description: "Bind wiki pages to a work item; each entry carries uuid, title, and position.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"task_uuid": uuid16Schema("The work item uuid."),
				"pages": map[string]any{
					"type":     "array",
					"minItems": 1,
					"items": objectSchema(map[string]any{
						"uuid":     uuid8Schema("The wiki page uuid."),
						"title":    boundedStringSchema("The wiki page title.", 256),
						"position": integerSchema("The sort position."),
					}, []string{"uuid", "title", "position"}),
					"description": "The pages to bind.",
				},
			}, []string{"team_uuid", "task_uuid", "pages"}),
			OutputSchema: rawObjectSchema("The ONES acknowledgement."),
		},
		{
			Name:        ActionUnbindTaskPage,
			Summary:     "Unlink a Wiki page from a work item",
			Description: "Remove one wiki page binding from a work item.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
				"task_uuid": uuid16Schema("The work item uuid."),
				"page": objectSchema(map[string]any{
					"uuid":  uuid8Schema("The wiki page uuid."),
					"title": stringSchema("The wiki page title."),
				}, []string{"uuid", "title"}),
			}, []string{"team_uuid", "task_uuid", "page"}),
			OutputSchema: rawObjectSchema("The ONES acknowledgement."),
		},
	}
}

func spaceActions() []registry.DefinitionAction {
	return []registry.DefinitionAction{
		{
			Name:        ActionListSpaces,
			Summary:     "List Wiki spaces",
			Description: "List the wiki spaces (页面组) of the team.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid": teamUUIDSchema,
			}, []string{"team_uuid"}),
			OutputSchema: objectSchema(map[string]any{
				"spaces": rawArraySchema("spaces", "The wiki space objects."),
			}, nil),
		},
		{
			Name:        ActionCreateSpace,
			Summary:     "Create a Wiki space",
			Description: "Create a wiki space with a title and description.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":   teamUUIDSchema,
				"title":       nonEmptyStringSchema("The space title."),
				"description": nonEmptyStringSchema("The space description."),
			}, []string{"team_uuid", "title", "description"}),
			OutputSchema: rawObjectSchema("The created space object."),
		},
		{
			Name:        ActionGetSpace,
			Summary:     "Get a Wiki space",
			Description: "Get one wiki space by uuid.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":  teamUUIDSchema,
				"space_uuid": uuid8Schema("The space uuid."),
			}, []string{"team_uuid", "space_uuid"}),
			OutputSchema: rawObjectSchema("The wiki space object."),
		},
		{
			Name:        ActionUpdateSpace,
			Summary:     "Update a Wiki space",
			Description: "Update the title or description of a wiki space.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":   teamUUIDSchema,
				"space_uuid":  uuid8Schema("The space uuid."),
				"title":       nonEmptyStringSchema("The new title."),
				"description": nonEmptyStringSchema("The new description."),
			}, []string{"team_uuid", "space_uuid", "title", "description"}),
			OutputSchema: rawObjectSchema("The ONES acknowledgement."),
		},
		{
			Name:        ActionDeleteSpace,
			Summary:     "Delete a Wiki space",
			Description: "Permanently delete a wiki space and its pages.",
			Safety:      destructiveSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":  teamUUIDSchema,
				"space_uuid": uuid8Schema("The space uuid."),
			}, []string{"team_uuid", "space_uuid"}),
			OutputSchema: rawObjectSchema("The ONES acknowledgement."),
		},
	}
}

// publishOutcomeSchema is the shared draft->publish output contract of
// create-page and update-page: the draft always rides along, the explicit
// published flag distinguishes success from the business-conflict partial
// envelope, and the page is null exactly when unpublished.
func publishOutcomeSchema(draftDescription string) map[string]any {
	return objectSchema(map[string]any{
		"draft":         rawObjectSchema(draftDescription),
		"published":     booleanSchema("Whether the draft was published as a page."),
		"page":          nullableRawObjectSchema("The publish result with page_uuid, or null when unpublished."),
		"publish_error": stringSchema("Stable error message when publishing failed; the draft survives for a retry."),
	}, []string{"draft", "published"})
}

func pageActions() []registry.DefinitionAction {
	return []registry.DefinitionAction{
		{
			Name:        ActionListPages,
			Summary:     "List Wiki pages",
			Description: "List the pages of a space; status 1 returns live pages and 2 deleted pages.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":  teamUUIDSchema,
				"space_uuid": uuid8Schema("The space uuid."),
				"status": map[string]any{
					"type": "integer", "enum": []any{1, 2},
					"description": "1 for live pages, 2 for deleted pages.",
				},
			}, []string{"team_uuid", "space_uuid", "status"}),
			OutputSchema: objectSchema(map[string]any{
				"pages": rawArraySchema("pages", "The wiki page objects."),
				"total": nullableIntegerSchema("The page count when present."),
			}, nil),
		},
		{
			Name:        ActionGetPage,
			Summary:     "Get a Wiki page",
			Description: "Get one wiki page; version selects a historical version, default the latest.",
			Safety:      readSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":  teamUUIDSchema,
				"space_uuid": uuid8Schema("The space uuid."),
				"page_uuid":  uuid8Schema("The page uuid."),
				"version":    nullableIntegerSchema("Optional historical version."),
			}, []string{"team_uuid", "space_uuid", "page_uuid"}),
			OutputSchema: rawObjectSchema("The wiki page object with content."),
		},
		{
			Name:        ActionCreatePage,
			Summary:     "Create and publish a Wiki page",
			Description: "Create a wiki page through the real draft flow: create a draft then publish it. On publish failure the draft uuid is returned so the content is not lost.",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":        teamUUIDSchema,
				"space_uuid":       uuid8Schema("The space uuid."),
				"title":            nonEmptyStringSchema("The page title."),
				"content":          stringSchema("The page content."),
				"parent_page_uuid": nullableUUID8Schema("Optional parent page uuid."),
			}, []string{"team_uuid", "space_uuid", "title", "content"}),
			OutputSchema: publishOutcomeSchema("The created draft object."),
		},
		{
			Name:        ActionUpdatePage,
			Summary:     "Update and publish a Wiki page",
			Description: "Update a wiki page through the page-draft flow: create a page draft then publish it. is_forced overrides version conflicts (819).",
			Safety:      writeSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":  teamUUIDSchema,
				"space_uuid": uuid8Schema("The space uuid."),
				"page_uuid":  uuid8Schema("The page uuid to update."),
				"title":      nonEmptyStringSchema("The new title."),
				"content":    stringSchema("The new content."),
				"is_forced":  booleanSchema("Publish over a version conflict."),
			}, []string{"team_uuid", "space_uuid", "page_uuid", "title", "content"}),
			OutputSchema: publishOutcomeSchema("The created page draft object."),
		},
		{
			Name:        ActionDeletePage,
			Summary:     "Delete a Wiki page",
			Description: "Permanently delete a wiki page (moved to the space recycle bin).",
			Safety:      destructiveSafety(),
			InputSchema: objectSchema(map[string]any{
				"team_uuid":  teamUUIDSchema,
				"space_uuid": uuid8Schema("The space uuid."),
				"page_uuid":  uuid8Schema("The page uuid."),
			}, []string{"team_uuid", "space_uuid", "page_uuid"}),
			OutputSchema: rawObjectSchema("The ONES acknowledgement."),
		},
	}
}
