package rest

import (
	"encoding/json"
	"net/http"
	"strconv"
)

var extensionRestOpenAPISpec = mustBuildExtensionRestOpenAPISpec()
var githubCompatibleOpenAPISpec = mustBuildGitHubCompatibleOpenAPISpec()

const extensionOpenAPIPrefix = "/api/ext/v1"
const githubCompatibleOpenAPIPrefix = "/api/v3"

// GetGitHubCompatibleOpenAPI handles GET /api/v3/openapi.json.
func (d *Deps) GetGitHubCompatibleOpenAPI(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(githubCompatibleOpenAPISpec)
}

// GetOpenAPI handles GET /api/ext/v1/openapi.json.
func (d *Deps) GetOpenAPI(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(extensionRestOpenAPISpec)
}

func mustBuildExtensionRestOpenAPISpec() []byte {
	body := map[string]any{
		"openapi": "3.0.3",
		"info": map[string]any{
			"title":       "agent-git-service Extension REST API",
			"version":     "1.0.0",
			"description": "Machine-readable contract for extension REST APIs under /api/ext/v1.",
		},
		"servers": []map[string]any{{"url": "/"}},
		"components": map[string]any{
			"securitySchemes": map[string]any{
				"tokenAuth": map[string]any{
					"type":        "apiKey",
					"in":          "header",
					"name":        "Authorization",
					"description": "Use the GitHub-compatible Authorization header format: `token <access-token>`.",
				},
			},
		},
		"x-agent-git-service": map[string]any{
			"api_surface": map[string]any{
				"extension_prefix":         extensionOpenAPIPrefix,
				"github_compatible_prefix": githubCompatibleOpenAPIPrefix,
			},
			"compatibility_deltas": []map[string]any{
				{
					"id":       "issues-list-omits-body",
					"path":     "/api/v3/repos/{owner}/{repo}/issues",
					"summary":  "List issues omits body content in REST list responses.",
					"evidence": "internal/service/issue.go: ListIssuesForREST returns issues for the REST list endpoint while omitting the body payload.",
				},
				{
					"id":       "branch-protection-monolithic",
					"path":     "/api/v3/repos/{owner}/{repo}/branches/{branch}/protection",
					"summary":  "Branch protection supports a monolithic protection document plus selected GitHub-style subresources, but not the full branch-protection subresource tree.",
					"evidence": "internal/router/router.go routes branch protection through wildcard branch handlers; internal/rest/handlers_branch.go dispatches selected subresources.",
				},
				{
					"id":       "extension-canonical-prefix",
					"path":     "/api/ext/v1/openapi.json",
					"summary":  "Extension APIs are published only under /api/ext/v1; /api/v3 is reserved for GitHub-compatible routes.",
					"evidence": "internal/router/router.go registers extension routes on /api/ext/v1.",
				},
			},
		},
		"paths": buildRESTOpenAPIPaths(),
	}
	out, err := json.MarshalIndent(body, "", "  ")
	if err != nil {
		panic(err)
	}
	return out
}

func mustBuildGitHubCompatibleOpenAPISpec() []byte {
	body := map[string]any{
		"openapi": "3.0.3",
		"info": map[string]any{
			"title":       "agent-git-service GitHub-Compatible REST API",
			"version":     "1.0.0",
			"description": "Machine-readable local compatibility contract for GitHub-shaped REST APIs under /api/v3. This document describes local compatibility behavior and is not a claim of strict GitHub.com parity.",
		},
		"servers": []map[string]any{{"url": "/"}},
		"components": map[string]any{
			"securitySchemes": map[string]any{
				"tokenAuth": map[string]any{
					"type":        "apiKey",
					"in":          "header",
					"name":        "Authorization",
					"description": "Use the GitHub-compatible Authorization header format: `token <access-token>`.",
				},
			},
			"schemas": githubStackOpenAPISchemas(),
		},
		"x-agent-git-service": map[string]any{
			"api_surface": map[string]any{
				"github_compatible_prefix": githubCompatibleOpenAPIPrefix,
				"extension_prefix":         extensionOpenAPIPrefix,
			},
			"compatibility_deltas": []map[string]any{
				{
					"id":       "issues-list-omits-body",
					"path":     "/api/v3/repos/{owner}/{repo}/issues",
					"summary":  "List issues omits body content in REST list responses.",
					"evidence": "internal/service/issue.go: ListIssuesForREST returns issues for the REST list endpoint while omitting the body payload.",
				},
				{
					"id":       "branch-protection-monolithic",
					"path":     "/api/v3/repos/{owner}/{repo}/branches/{branch}/protection",
					"summary":  "Branch protection supports a monolithic protection document plus selected GitHub-style subresources, but not the full branch-protection subresource tree.",
					"evidence": "internal/router/router.go routes branch protection through wildcard branch handlers; internal/rest/handlers_branch.go dispatches selected subresources.",
				},
			},
			"route_families": []string{
				"discovery",
				"current user",
				"repositories",
				"issues",
				"pull requests",
				"branches and commits",
				"contents and Git database",
				"organizations and teams",
				"releases",
				"notifications",
				"search",
			},
		},
		"paths": buildGitHubCompatibleOpenAPIPaths(),
	}
	out, err := json.MarshalIndent(body, "", "  ")
	if err != nil {
		panic(err)
	}
	return out
}

func buildGitHubCompatibleOpenAPIPaths() map[string]any {
	return map[string]any{
		"/api/v3": map[string]any{
			"get": operation("getGitHubCompatibleAPIDiscovery", "Get GitHub-compatible REST discovery links.", nil, nil, nil, response(200, "API discovery document")),
		},
		"/api/v3/": map[string]any{
			"get": operation("getGitHubCompatibleAPIDiscoveryTrailingSlash", "Get GitHub-compatible REST discovery links.", nil, nil, nil, response(200, "API discovery document")),
		},
		"/api/v3/openapi.json": map[string]any{
			"get": operation("getGitHubCompatibleOpenAPISpec", "Get the published OpenAPI compatibility contract for GitHub-shaped REST APIs.", nil, nil, nil, response(200, "OpenAPI document")),
		},
		"/api/v3/meta": map[string]any{
			"get": operation("getServerMeta", "Get GitHub-compatible server metadata.", nil, nil, nil, response(200, "Server metadata returned")),
		},
		"/api/v3/rate_limit": map[string]any{
			"get": operation("getRateLimit", "Get GitHub-compatible local rate-limit state.", nil, nil, nil, response(200, "Rate-limit state returned")),
		},
		"/api/v3/user": map[string]any{
			"get": operation("getAuthenticatedUser", "Get the authenticated user.", auth(), nil, nil, response(200, "Authenticated user returned")),
			"patch": operation("updateAuthenticatedUser", "Update mutable profile fields for the authenticated user. Email is identity-provider-managed and may only be supplied unchanged.", auth(), jsonBody(true, map[string]any{
				"name":     map[string]any{"type": "string", "maxLength": 255},
				"email":    map[string]any{"type": "string", "format": "email", "maxLength": 255},
				"bio":      map[string]any{"type": "string", "maxLength": 1024},
				"company":  map[string]any{"type": "string", "maxLength": 255},
				"blog":     map[string]any{"type": "string", "maxLength": 2048},
				"location": map[string]any{"type": "string", "maxLength": 255},
			}, nil), nil, responses(
				response(200, "Authenticated user updated"),
				response(400, "Invalid request body"),
				response(401, "Authentication required"),
				response(422, "Profile validation failed"),
			)),
		},
		"/api/v3/orgs/{org}": map[string]any{
			"get": operation("getOrganization", "Get an organization.", nil, nil, pathParams(param("org", "string")), response(200, "Organization returned")),
			"patch": operation("updateOrganization", "Update mutable profile metadata and default repository permission for an organization. Active organization owner access required.", auth(), jsonBody(true, map[string]any{
				"name":                          map[string]any{"type": "string", "maxLength": 255},
				"description":                   map[string]any{"type": "string", "maxLength": 1024},
				"email":                         map[string]any{"type": "string", "format": "email", "maxLength": 255},
				"company":                       map[string]any{"type": "string", "maxLength": 255},
				"blog":                          map[string]any{"type": "string", "maxLength": 2048},
				"location":                      map[string]any{"type": "string", "maxLength": 255},
				"default_repository_permission": map[string]any{"type": "string", "enum": []string{"none", "read", "write", "admin"}},
			}, nil), nil, responses(response(200, "Organization updated"), response(401, "Authentication required"), response(403, "Organization owner access required"), response(422, "Organization profile validation failed"))),
		},
		"/api/v3/user/orgs": map[string]any{
			"get": operation("listUserOrgs", "List organizations for the authenticated user.", auth(), nil, nil, response(200, "Organization list returned")),
		},
		"/api/v3/user/repos": map[string]any{
			"get":  operation("listUserRepos", "List repositories for the authenticated user.", auth(), nil, nil, response(200, "Repository list returned")),
			"post": operation("createUserRepo", "Create a repository for the authenticated user.", auth(), nil, nil, response(201, "Repository created")),
		},
		"/api/v3/repos/{owner}/{repo}": map[string]any{
			"get":    operation("getRepo", "Get a repository.", nil, nil, pathParams(param("owner", "string"), param("repo", "string")), response(200, "Repository returned")),
			"patch":  operation("updateRepo", "Update a repository.", auth(), nil, pathParams(param("owner", "string"), param("repo", "string")), response(200, "Repository updated")),
			"delete": operation("deleteRepo", "Delete a repository.", auth(), nil, pathParams(param("owner", "string"), param("repo", "string")), response(204, "Repository deleted")),
		},
		"/api/v3/repos/{owner}/{repo}/issues": map[string]any{
			"get":  operation("listIssues", "List repository issues.", nil, nil, pathParams(param("owner", "string"), param("repo", "string")), response(200, "Issue list returned")),
			"post": operation("createIssue", "Create an issue.", auth(), nil, pathParams(param("owner", "string"), param("repo", "string")), response(201, "Issue created")),
		},
		"/api/v3/repos/{owner}/{repo}/issues/{number}": map[string]any{
			"get":   operation("getIssue", "Get an issue.", nil, nil, pathParams(param("owner", "string"), param("repo", "string"), param("number", "integer")), response(200, "Issue returned")),
			"patch": operation("updateIssue", "Update an issue.", auth(), nil, pathParams(param("owner", "string"), param("repo", "string"), param("number", "integer")), response(200, "Issue updated")),
		},
		"/api/v3/repos/{owner}/{repo}/issues/{number}/comments": map[string]any{
			"get":  operation("listIssueComments", "List issue comments.", nil, nil, pathParams(param("owner", "string"), param("repo", "string"), param("number", "integer")), response(200, "Issue comments returned")),
			"post": operation("createIssueComment", "Create an issue comment.", auth(), nil, pathParams(param("owner", "string"), param("repo", "string"), param("number", "integer")), response(201, "Issue comment created")),
		},
		"/api/v3/repos/{owner}/{repo}/pulls": map[string]any{
			"get":  operation("listPullRequests", "List pull requests.", nil, nil, pathParams(param("owner", "string"), param("repo", "string")), response(200, "Pull request list returned")),
			"post": operation("createPullRequest", "Create a pull request.", auth(), nil, pathParams(param("owner", "string"), param("repo", "string")), response(201, "Pull request created")),
		},
		"/api/v3/repos/{owner}/{repo}/pulls/{number}": map[string]any{
			"get":   operation("getPullRequest", "Get a pull request.", nil, nil, pathParams(param("owner", "string"), param("repo", "string"), param("number", "integer")), response(200, "Pull request returned")),
			"patch": operation("updatePullRequest", "Update a pull request.", auth(), nil, pathParams(param("owner", "string"), param("repo", "string"), param("number", "integer")), response(200, "Pull request updated")),
		},
		"/api/v3/repos/{owner}/{repo}/stacks": map[string]any{
			"get": operation("listPullRequestStacks", "List explicitly persisted pull request Stacks, newest Stack number first.", nil, nil, append(pathParams(
				param("owner", "string"), param("repo", "string"),
			), queryParams(param("pull_request", "integer"), param("page", "integer"), param("per_page", "integer"))...), responses(
				jsonResponse(200, "Stack list returned", map[string]any{"type": "array", "items": schemaRef("PullRequestStackMinimal")}), response(404, "Resource not found"), response(422, "Validation failed"),
			)),
			"post": operation("createPullRequestStack", "Create a linear pull request Stack from bottom to top.", auth(), jsonBody(true, map[string]any{
				"pull_requests": map[string]any{"type": "array", "minItems": 2, "maxItems": 100, "items": map[string]any{"type": "integer"}},
			}, []string{"pull_requests"}), pathParams(param("owner", "string"), param("repo", "string")), responses(
				jsonResponse(201, "Stack created", schemaRef("PullRequestStack")), response(404, "Resource not found"), response(422, "Validation failed"),
			)),
		},
		"/api/v3/repos/{owner}/{repo}/stacks/{stack_number}": map[string]any{
			"get": operation("getPullRequestStack", "Get one pull request Stack.", nil, nil, pathParams(
				param("owner", "string"), param("repo", "string"), param("stack_number", "integer"),
			), responses(jsonResponse(200, "Stack returned", schemaRef("PullRequestStack")), response(404, "Resource not found"))),
		},
		"/api/v3/repos/{owner}/{repo}/stacks/{stack_number}/add": map[string]any{
			"post": operation("addPullRequestsToStack", "Append a linear bottom-to-top delta above the current Stack top.", auth(), jsonBody(true, map[string]any{
				"pull_requests": map[string]any{"type": "array", "minItems": 1, "maxItems": 100, "items": map[string]any{"type": "integer"}},
			}, []string{"pull_requests"}), pathParams(
				param("owner", "string"), param("repo", "string"), param("stack_number", "integer"),
			), responses(jsonResponse(200, "Stack updated", schemaRef("PullRequestStack")), response(404, "Resource not found"),
				response(409, "Stack is being modified"), response(422, "Validation failed"))),
		},
		"/api/v3/repos/{owner}/{repo}/stacks/{stack_number}/unstack": map[string]any{
			"post": operation("unstackPullRequestStack", "Remove inactive unmerged pull requests without changing PR bases or Git history.", auth(), nil, pathParams(
				param("owner", "string"), param("repo", "string"), param("stack_number", "integer"),
			), responses(jsonResponse(200, "Residual Stack returned", schemaRef("PullRequestStack")), response(204, "Stack dissolved"),
				response(404, "Resource not found"), response(409, "Stack is being modified"), response(422, "Every pull request is locked"))),
		},
		"/api/v3/repos/{owner}/{repo}/pulls/{pull_number}/merge-async": map[string]any{
			"put": operation("mergePullRequestAsync", "Submit an asynchronous direct or Merge Queue pull request merge.", auth(), jsonBody(false, map[string]any{
				"commit_title": stringSchema("Merge commit title."), "commit_message": stringSchema("Merge commit body."),
				"sha":          stringSchema("Expected current pull request head SHA."),
				"merge_method": map[string]any{"type": "string", "enum": []string{"merge", "squash", "rebase"}},
				"merge_action": map[string]any{"type": "string", "enum": []string{"default", "direct_merge", "merge_queue"}},
			}, nil), pathParams(param("owner", "string"), param("repo", "string"), param("pull_number", "integer")), responses(
				jsonResponse(200, "Already merged or already queued", schemaRef("PullRequestAsyncMergeResult")), jsonResponse(202, "Async merge accepted", schemaRef("PullRequestAsyncMergeResult")),
				jsonResponse(400, "Pull request is not ready", schemaRef("PullRequestAsyncMergeResult")), response(403, "Forbidden"), response(404, "Resource not found"),
				jsonResponse(409, "An async merge request already exists", schemaRef("PullRequestAsyncMergeResult")), response(422, "Validation failed"),
			)),
		},
		"/api/v3/repos/{owner}/{repo}/pulls/{pull_number}/merge-async/{uuid}": map[string]any{
			"get": operation("getPullRequestAsyncMerge", "Get a retained asynchronous merge result.", nil, nil, pathParams(
				param("owner", "string"), param("repo", "string"), param("pull_number", "integer"), param("uuid", "string"),
			), responses(jsonResponse(200, "Async merge result returned", schemaRef("PullRequestAsyncMergeResult")), response(403, "Forbidden"), response(404, "Resource not found"))),
		},
	}
}

func buildRESTOpenAPIPaths() map[string]any {
	return map[string]any{
		"/api/ext/v1": map[string]any{
			"get": operation("getExtensionAPIDiscovery", "Get extension API discovery links.", nil, nil, nil, response(200, "extension API discovery document")),
		},
		"/api/ext/v1/": map[string]any{
			"get": operation("getExtensionAPIDiscoveryTrailingSlash", "Get extension API discovery links.", nil, nil, nil, response(200, "extension API discovery document")),
		},
		"/api/ext/v1/openapi.json": map[string]any{
			"get": operation("getRESTOpenAPISpec", "Get the published OpenAPI contract for extension REST APIs.", nil, nil, nil, response(200, "OpenAPI document")),
		},
		"/api/ext/v1/agents": map[string]any{
			"post": operation("createAgent", "Register a new agent identity.", nil, jsonBody(true, map[string]any{
				"prefix_login":      stringSchema("Optional login prefix for the created agent account."),
				"default_repo_name": stringSchema("Optional default repository name for the created agent."),
			}, nil), nil, response(201, "Agent created")),
		},
		"/api/ext/v1/agent-invites": map[string]any{
			"post": operation("createAgentInvite", "Create an invite token used to bind an agent to a user.", auth(), jsonBody(false, map[string]any{
				"repo_grants": map[string]any{"type": "array", "items": map[string]any{"type": "object", "properties": map[string]any{"repo_full_name": stringSchema("Repository full name to grant during bind."), "permission": stringSchema("Requested permission (read/write/admin).")}}},
				"team_grants": map[string]any{"type": "array", "items": map[string]any{"type": "object", "properties": map[string]any{"org": stringSchema("Organization login for a team grant."), "team_slug": stringSchema("Team slug to grant during bind."), "role": stringSchema("Team role (member/maintainer).")}}},
			}, nil), nil, response(201, "Agent invite created")),
		},
		"/api/ext/v1/agent-bindings/confirm": map[string]any{
			"post": operation("confirmAgentBinding", "Confirm an agent binding using an invite token.", auth(), jsonBody(true, map[string]any{
				"invite_token": stringSchema("Invite token issued by POST /api/ext/v1/agent-invites."),
			}, []string{"invite_token"}), nil, response(201, "Binding confirmed")),
		},
		"/api/ext/v1/agent-bindings/{agent_login}": map[string]any{
			"patch": operation("renameBoundAgent", "Rename a bound agent's display name.", auth(), jsonBody(true, map[string]any{
				"name": stringSchema("New display name for the bound agent."),
			}, []string{"name"}), pathParams(param("agent_login", "string")), response(200, "Agent renamed")),
			"delete": operation("unbindAgent", "Detach a bound agent and revoke only temporary human console switch sessions. The agent's long-lived token, account, memory repository, and independently managed grants are retained.", auth(), nil, pathParams(param("agent_login", "string")), response(200, "Agent unbound")),
		},
		"/api/ext/v1/agent-bindings/{agent_login}/reset-token": map[string]any{
			"post": operation("resetAgentToken", "Rotate the token for a bound agent login.", auth(), nil, pathParams(param("agent_login", "string")), response(200, "Token rotated")),
		},
		"/api/ext/v1/agent-bindings/{agent_login}/switch-session": map[string]any{
			"post": operation("switchAgentSession", "Create a temporary console session for a bound agent without rotating its existing tokens.", auth(), nil, pathParams(param("agent_login", "string")), response(200, "Switch session created")),
		},
		"/api/ext/v1/agent-bindings/{agent_login}/refresh-session": map[string]any{
			"post": operation("refreshAgentSwitchSession", "Refresh an active bound-agent switch session before it expires.", auth(), nil, pathParams(param("agent_login", "string")), response(200, "Switch session refreshed")),
		},
		"/api/ext/v1/oidc/device/code": map[string]any{
			"post": operation("createOIDCDeviceCode", "Start a generic OIDC device-code login flow.", nil, nil, nil, response(200, "Device code issued")),
		},
		"/api/ext/v1/oidc/session": map[string]any{
			"post": operation("exchangeOIDCSession", "Exchange generic OIDC session data for a local session.", nil, jsonBody(true, map[string]any{
				"device_code": stringSchema("OIDC device code previously issued to the client."),
			}, []string{"device_code"}), nil, response(200, "Session established")),
		},
		"/api/ext/v1/oidc/callback": map[string]any{
			"post": operation("handleOIDCCallback", "Handle the generic OIDC callback payload.", nil, jsonBody(true, map[string]any{
				"id_token": stringSchema("OIDC ID token returned from the login redirect flow."),
			}, []string{"id_token"}), nil, response(200, "Callback processed")),
		},
		"/api/ext/v1/oidc/lookup": map[string]any{
			"post": operation("lookupOIDCIdentity", "Resolve a generic OIDC identity to a local user.", nil, jsonBody(true, map[string]any{
				"id_token": stringSchema("OIDC ID token to validate and map to a local user."),
			}, []string{"id_token"}), nil, response(200, "Identity resolved")),
		},
		"/api/ext/v1/oauth/device/approve": map[string]any{
			"post": operation("approveOAuthDeviceCode", "Approve an OAuth device code for the authenticated human user.", auth(), jsonAndFormBody(true, map[string]any{
				"user_code": stringSchema("User code displayed to the human during the OAuth device flow."),
			}, []string{"user_code"}), nil, response(200, "Device code approved")),
		},
		"/api/ext/v1/oauth/device/reject": map[string]any{
			"post": operation("rejectOAuthDeviceCode", "Reject an OAuth device code for the authenticated human user.", auth(), jsonAndFormBody(true, map[string]any{
				"user_code": stringSchema("User code displayed to the human during the OAuth device flow."),
				"reason":    stringSchema("Optional rejection reason recorded in the device-code audit log."),
			}, []string{"user_code"}), nil, response(200, "Device code rejected")),
		},
		"/auth/connected/login": map[string]any{
			"get": operation("startConnectedLogin", "Redirect the browser to the configured connected login provider.", nil, nil, nil, response(302, "Redirect to connected login")),
		},
		"/auth/connected/callback": map[string]any{
			"get": operation("handleConnectedCallback", "Exchange a connected login authorization code for a local session.", nil, nil, queryParams(
				param("code", "string"),
				param("error", "string"),
				param("state", "string"),
			), map[string]any{
				"200": map[string]any{"description": "Direct agent callback without browser state returns durable token JSON; browser callback without console redirect returns a one-time local authorization code JSON."},
				"302": map[string]any{"description": "Browser callback redirects to the console with a one-time local authorization code and PKCE verifier cookie."},
			}),
		},
		"/api/ext/v1/analytics/events": map[string]any{
			"post": operation("createAnalyticsEvents", "Record public frontend analytics events for Prometheus aggregation.", nil, jsonBody(true, map[string]any{
				"events": map[string]any{
					"type":        "array",
					"description": "Optional batch of frontend analytics events.",
					"items": map[string]any{
						"type": "object",
						"properties": map[string]any{
							"event_name":      stringSchema("Event identifier emitted by the frontend."),
							"name":            stringSchema("Legacy alias for event_name."),
							"source":          stringSchema("Frontend surface that emitted the event."),
							"path":            stringSchema("Current page path or URL."),
							"target_path":     stringSchema("Destination path for click-through events."),
							"page":            stringSchema("Normalized page identifier, when already computed by the client."),
							"platform":        stringSchema("Client platform or install target."),
							"location":        stringSchema("UI location where the event originated."),
							"button_id":       stringSchema("Frontend control identifier for install/CTA events."),
							"success":         map[string]any{"type": "boolean", "description": "Whether the tracked operation completed successfully."},
							"traffic_source":  stringSchema("Explicit traffic source classification."),
							"utm_source":      stringSchema("UTM source query parameter observed by the client."),
							"referrer_domain": stringSchema("Referrer hostname or domain observed by the client."),
						},
					},
				},
				"event_name":      stringSchema("Single-event event identifier when not sending a batch."),
				"name":            stringSchema("Legacy alias for event_name when not sending a batch."),
				"source":          stringSchema("Frontend surface that emitted the single event."),
				"path":            stringSchema("Current page path or URL for the single event."),
				"target_path":     stringSchema("Destination path for a single click-through event."),
				"page":            stringSchema("Normalized page identifier for the single event."),
				"platform":        stringSchema("Client platform or install target."),
				"location":        stringSchema("UI location where the single event originated."),
				"button_id":       stringSchema("Frontend control identifier for the single event."),
				"success":         map[string]any{"type": "boolean", "description": "Whether the single tracked operation completed successfully."},
				"traffic_source":  stringSchema("Explicit traffic source classification for the single event."),
				"utm_source":      stringSchema("UTM source query parameter observed by the client."),
				"referrer_domain": stringSchema("Referrer hostname or domain observed by the client."),
			}, nil), nil, response(202, "Accepted analytics event payload for metric aggregation")),
		},
		"/api/ext/v1/user/agents": map[string]any{
			"get": operation("listBoundAgents", "List agents bound to the authenticated user.", auth(), nil, nil, response(200, "Bound agents returned")),
		},
		"/api/ext/v1/viewer/summary": map[string]any{
			"get": operation("getViewerSummary", "Get the authenticated viewer's console summary.", auth(), nil, nil, response(200, "Viewer summary returned")),
		},
		"/api/ext/v1/user/avatar": map[string]any{
			"put":    operation("uploadViewerAvatar", "Upload a PNG, JPEG, or GIF profile picture up to 1 MiB and 3000px on either edge.", auth(), multipartFileBody(), nil, responses(response(200, "Updated user returned"), response(400, "Malformed multipart form"), response(413, "Image exceeds 1 MiB"), response(415, "Unsupported or invalid image"))),
			"delete": operation("deleteViewerAvatar", "Remove the authenticated user's custom profile picture and return the generated identicon.", auth(), nil, nil, response(200, "Updated user returned")),
		},
		"/api/ext/v1/orgs/{org}/avatar": map[string]any{
			"put":    operation("uploadOrganizationAvatar", "Upload an organization profile picture. Active organization owners only.", auth(), multipartFileBody(), pathParams(param("org", "string")), responses(response(200, "Updated organization returned"), response(400, "Malformed multipart form"), response(403, "Organization owner access required"), response(413, "Image exceeds 1 MiB"), response(415, "Unsupported or invalid image"))),
			"delete": operation("deleteOrganizationAvatar", "Remove an organization's custom profile picture. Active organization owners only.", auth(), nil, pathParams(param("org", "string")), responses(response(200, "Updated organization returned"), response(403, "Organization owner access required"))),
		},
		"/api/ext/v1/orgs/{org}/profile/pins": map[string]any{
			"get": operation("getOrganizationProfilePins", "Get the organization profile's curated repositories. Member-only pins are included only for active members.", nil, nil, pathParams(param("org", "string")), response(200, "Organization profile pins returned")),
			"put": operation("replaceOrganizationProfilePins", "Atomically replace public and member organization profile pins. Active organization owner access required; maximum six per audience.", auth(), jsonBody(true, map[string]any{
				"repositories": map[string]any{"type": "array", "maxItems": 12, "items": map[string]any{"type": "object", "required": []string{"name"}, "properties": map[string]any{
					"name":     map[string]any{"type": "string"},
					"audience": map[string]any{"type": "string", "enum": []string{"public", "member"}},
				}},
				},
			}, nil), pathParams(param("org", "string")), responses(response(200, "Organization profile pins replaced"), response(403, "Organization owner access required"), response(422, "Invalid profile pins"))),
		},
		"/api/ext/v1/sync/github/installations": map[string]any{
			"get": operation("listRepositorySyncGitHubInstallations", "Return the authenticated AGS user's connected GitHub identity and its authorized App installations.", auth(), nil, nil, response(200, "Connected GitHub account and installations returned")),
		},
		"/api/ext/v1/sync/github/account": map[string]any{
			"delete": operation("disconnectRepositorySyncGitHubAccount", "Disconnect the authenticated AGS user's GitHub identity from repository-sync setup. Existing bindings and the GitHub App installation are not removed.", auth(), nil, nil, response(204, "GitHub account disconnected")),
		},
		"/api/ext/v1/sync/github/install/start": map[string]any{
			"post": operation("startRepositorySyncGitHubInstall", "Start the browser-based GitHub App installation and authorization flow.", auth(), jsonBody(true, map[string]any{
				"return_to": map[string]any{"type": "string"},
			}, nil), nil, response(201, "GitHub installation URL returned")),
		},
		"/api/ext/v1/sync/github/authorize/start": map[string]any{
			"post": operation("startRepositorySyncGitHubAuthorize", "Connect the authenticated AGS user to a verified GitHub identity and refresh that user's repository access.", auth(), jsonBody(true, map[string]any{
				"return_to": map[string]any{"type": "string"},
			}, nil), nil, response(201, "GitHub authorization URL returned")),
		},
		"/api/ext/v1/sync/github/install/callback": map[string]any{
			"get": operation("completeRepositorySyncGitHubInstall", "Complete GitHub App installation authorization and redirect to the AGS console.", nil, nil, queryParams(
				map[string]any{"name": "state", "in": "query", "required": true, "schema": map[string]any{"type": "string"}},
				map[string]any{"name": "code", "in": "query", "required": false, "schema": map[string]any{"type": "string"}},
				map[string]any{"name": "installation_id", "in": "query", "required": false, "schema": map[string]any{"type": "integer"}},
			), response(302, "Redirect to the AGS sync page")),
		},
		"/api/ext/v1/sync/github/repositories": map[string]any{
			"get": operation("listRepositorySyncGitHubRepositories", "List repositories available through the authenticated AGS user's GitHub authorization.", auth(), nil, queryParams(
				map[string]any{"name": "connection_id", "in": "query", "required": false, "schema": map[string]any{"type": "string"}},
			), response(200, "GitHub repositories returned")),
		},
		"/api/ext/v1/sync/overview": map[string]any{
			"get": operation("getRepositorySyncOverview", "Get repository sync connection health, latest runs, and active progress for the authenticated repository administrator in one aggregate response.", auth(), nil, nil, response(200, "Repository sync overview returned")),
		},
		"/api/ext/v1/sync/bindings": map[string]any{
			"get": operation("listRepositorySyncBindings", "List repository sync bindings visible to the current repository administrator.", auth(), nil, queryParams(
				map[string]any{"name": "ags_repository_id", "in": "query", "required": false, "schema": map[string]any{"type": "integer", "minimum": 1}},
				map[string]any{"name": "status", "in": "query", "required": false, "schema": map[string]any{"type": "string"}},
				map[string]any{"name": "limit", "in": "query", "required": false, "schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 100}},
			), response(200, "Repository sync bindings returned")),
			"post": operation("createRepositorySyncBinding", "Bind one AGS repository to one GitHub repository. Side a is AGS, side b is GitHub, and exactly one side is authoritative.", auth(), jsonBody(true, map[string]any{
				"ags_repository_id":           map[string]any{"type": "integer", "minimum": 1},
				"github_repository_id":        stringSchema("Immutable GitHub repository ID or node ID."),
				"github_repository_full_name": stringSchema("Current GitHub owner/name locator."),
				"github_connection_id":        stringSchema("GitHub App installation connection reference."),
				"initial_authority":           stringSchema("ags or github."),
				"confirm_visibility_exposure": map[string]any{"type": "boolean", "description": "Required when the standby is more public than the authority; bound to current immutable identities, direction, and visibility."},
				"schedule": map[string]any{"type": "object", "properties": map[string]any{
					"mode": stringSchema("manual or scheduled."),
					"cron": stringSchema("Cron expression required for scheduled mode."),
				}},
			}, []string{"ags_repository_id", "github_repository_id", "github_connection_id", "initial_authority"}), nil, response(201, "Repository sync binding created")),
		},
		"/api/ext/v1/sync/bindings/{binding_id}": map[string]any{
			"get": operation("getRepositorySyncBinding", "Get one repository sync binding.", auth(), nil, pathParams(
				param("binding_id", "integer"),
			), response(200, "Repository sync binding returned")),
			"patch": operation("updateRepositorySyncBinding", "Pause, resume, close, or reschedule a repository sync binding. Authority cannot be changed here.", auth(), jsonBody(true, map[string]any{
				"status":                      stringSchema("active, paused, or closed."),
				"sync_enabled":                map[string]any{"type": "boolean", "description": "Enable scheduler only after a successful full apply in the current authority epoch."},
				"confirm_visibility_exposure": map[string]any{"type": "boolean", "description": "Refresh the direction- and visibility-bound exposure confirmation after repository visibility changes."},
				"schedule": map[string]any{"type": "object", "properties": map[string]any{
					"mode": stringSchema("manual or scheduled."),
					"cron": stringSchema("Cron expression required for scheduled mode."),
				}},
			}, nil), pathParams(param("binding_id", "integer")), response(200, "Repository sync binding updated")),
		},
		"/api/ext/v1/sync/bindings/{binding_id}/runs": map[string]any{
			"post": operation("createRepositorySyncRun", "Queue one run in the binding's current authority direction. The worker selects empty-target bootstrap or current-state diff automatically.", auth(), jsonBody(false, map[string]any{
				"mode":       stringSchema("apply or dry_run; defaults to apply."),
				"audit_mode": stringSchema("checkpoint or full; the first apply in an authority epoch is forced to full."),
			}, nil), pathParams(param("binding_id", "integer")), response(202, "Repository sync run queued")),
			"get": operation("listRepositorySyncRuns", "List recent runs for one repository sync binding.", auth(), nil, append(pathParams(
				param("binding_id", "integer"),
			), queryParams(map[string]any{
				"name": "limit", "in": "query", "required": false,
				"schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 100},
			})...), response(200, "Repository sync runs returned")),
		},
		"/api/ext/v1/sync/bindings/{binding_id}/handoff": map[string]any{
			"post": operation("createRepositorySyncHandoff", "Queue a planned authority handoff. The worker must complete final sync, reverse dry-run, and epoch switch before authority changes.", auth(), jsonBody(true, map[string]any{
				"to_side":                     stringSchema("Destination provider: ags or github."),
				"expected_authority_epoch":    map[string]any{"type": "integer", "minimum": 1},
				"authority_writes_stopped":    map[string]any{"type": "boolean"},
				"confirm_visibility_exposure": map[string]any{"type": "boolean"},
			}, []string{"to_side", "expected_authority_epoch", "authority_writes_stopped"}), pathParams(param("binding_id", "integer")), response(202, "Repository sync handoff queued")),
		},
		"/api/ext/v1/sync/runs": map[string]any{
			"get": operation("listRepositorySyncActivity", "List recent runs across all repository sync bindings the authenticated repository administrator can inspect.", auth(), nil, queryParams(
				map[string]any{"name": "after_id", "in": "query", "required": false, "schema": map[string]any{"type": "integer", "minimum": 1}, "description": "Return runs with IDs lower than this cursor. Results are ordered newest first."},
				map[string]any{"name": "status", "in": "query", "required": false, "schema": map[string]any{"type": "string"}, "description": "Filter by a run status or the active/running, attention/needs_attention, or all status groups."},
				map[string]any{"name": "limit", "in": "query", "required": false, "schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 100}},
			), response(200, "Repository sync activity returned")),
		},
		"/api/ext/v1/sync/runs/{run_id}": map[string]any{
			"get": operation("getRepositorySyncRun", "Get one repository sync run and its report.", auth(), nil, pathParams(
				param("run_id", "integer"),
			), response(200, "Repository sync run returned")),
		},
		"/api/ext/v1/sync/runs/{run_id}/progress": map[string]any{
			"get": operation("getRepositorySyncRunProgress", "Get lightweight live RunItem state counts for a repository sync run.", auth(), nil, pathParams(
				param("run_id", "integer"),
			), response(200, "Repository sync run progress returned")),
		},
		"/api/ext/v1/sync/runs/{run_id}/items": map[string]any{
			"get": operation("listRepositorySyncRunItems", "List object-level sync changes and attention details with cursor pagination.", auth(), nil, append(pathParams(
				param("run_id", "integer"),
			), queryParams(
				map[string]any{"name": "after_id", "in": "query", "required": false, "schema": map[string]any{"type": "integer", "minimum": 1}},
				map[string]any{"name": "state", "in": "query", "required": false, "schema": map[string]any{"type": "string"}},
				map[string]any{"name": "object_type", "in": "query", "required": false, "schema": map[string]any{"type": "string"}},
				map[string]any{"name": "action", "in": "query", "required": false, "schema": map[string]any{"type": "string"}},
				map[string]any{"name": "limit", "in": "query", "required": false, "schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 500}},
			)...), response(200, "Repository sync run items returned")),
		},
		"/api/ext/v1/sync/runs/{run_id}/cancel": map[string]any{
			"post": operation("cancelRepositorySyncRun", "Cancel a queued run or request cancellation at the next safe RunItem boundary.", auth(), nil, pathParams(
				param("run_id", "integer"),
			), response(200, "Repository sync cancellation recorded")),
		},
		"/api/ext/v1/sync/runs/{run_id}/recover": map[string]any{
			"post": operation("recoverRepositorySyncRun", "Requeue ambiguous external intents for read-back-only recovery. Unknown operations are never blindly retried.", auth(), nil, pathParams(
				param("run_id", "integer"),
			), response(202, "Repository sync recovery queued")),
		},
		"/api/ext/v1/imports/github": map[string]any{
			"post": operation("createGitHubImport", "Create a target-authorized staged GitHub import job. Source credentials remain in the external ags-migrate executor.", auth(), jsonBody(true, map[string]any{
				"source_owner":    stringSchema("GitHub source owner or organization."),
				"source_repo":     stringSchema("GitHub source repository name."),
				"source_api_url":  stringSchema("Optional GitHub API base URL. Defaults to https://api.github.com."),
				"source_html_url": stringSchema("Optional source browser URL."),
				"target_owner":    stringSchema("AGS target owner or organization."),
				"target_repo":     stringSchema("AGS target repository name."),
				"target_mode":     stringSchema("create (default) creates a new target repository; existing_empty permits an existing empty repository only when the caller is its admin."),
				"target_repository": map[string]any{
					"type": "object", "description": "Non-secret source repository settings copied only when target_mode=create. Source credentials never reach AGS.",
					"properties": map[string]any{
						"description":    stringSchema("Source repository description."),
						"visibility":     stringSchema("Source repository visibility."),
						"private":        map[string]any{"type": "boolean"},
						"default_branch": stringSchema("Source repository default branch."),
						"has_issues":     map[string]any{"type": "boolean"},
						"has_wiki":       map[string]any{"type": "boolean"},
					},
				},
				"options": map[string]any{
					"type":        "object",
					"description": "Selectable migration items. When no option is enabled, the importer enables the common migration set.",
					"properties": map[string]any{
						"mirror_git":                map[string]any{"type": "boolean"},
						"import_labels":             map[string]any{"type": "boolean"},
						"import_milestones":         map[string]any{"type": "boolean"},
						"import_issues":             map[string]any{"type": "boolean"},
						"import_issue_comments":     map[string]any{"type": "boolean"},
						"import_pull_requests":      map[string]any{"type": "boolean"},
						"import_pr_reviews":         map[string]any{"type": "boolean"},
						"import_pr_review_comments": map[string]any{"type": "boolean"},
						"import_wiki":               map[string]any{"type": "boolean"},
						"import_releases":           map[string]any{"type": "boolean"},
						"import_release_assets":     map[string]any{"type": "boolean"},
						"import_branch_protection":  map[string]any{"type": "boolean"},
						"import_webhooks":           map[string]any{"type": "boolean"},
						"import_collaborators":      map[string]any{"type": "boolean"},
						"import_teams":              map[string]any{"type": "boolean"},
						"import_secret_names":       map[string]any{"type": "boolean"},
						"import_actions_metadata":   map[string]any{"type": "boolean"},
						"max_release_asset_bytes":   map[string]any{"type": "integer", "minimum": 1},
					},
					"additionalProperties": false,
				},
			}, []string{"source_owner", "source_repo", "target_owner", "target_repo"}), nil, response(201, "GitHub import job created")),
		},
		"/api/ext/v1/imports/{import_id}": map[string]any{
			"get": operation("getImport", "Get one import job.", auth(), nil, pathParams(
				param("import_id", "integer"),
			), response(200, "Import job returned")),
		},
		"/api/ext/v1/imports/{import_id}/cancel": map[string]any{
			"post": operation("cancelImport", "Cancel a queued or running import job.", auth(), nil, pathParams(
				param("import_id", "integer"),
			), response(200, "Import job canceled")),
		},
		"/api/ext/v1/imports/{import_id}/apply": map[string]any{
			"post": operation("applyImportBatches", "Apply staged import batches into AGS native tables where writers are available.", auth(), jsonBody(false, map[string]any{
				"batch_limit": map[string]any{"type": "integer", "minimum": 0, "maximum": 100, "description": "Maximum supported staged batches to apply in this request. 0 applies all supported staged batches."},
				"skip_verify": map[string]any{"type": "boolean", "description": "Skip full staging/mapping verification after this apply request."},
			}, nil), append(pathParams(
				param("import_id", "integer"),
			), queryParams(map[string]any{
				"name":        "batch_limit",
				"in":          "query",
				"required":    false,
				"schema":      map[string]any{"type": "integer", "minimum": 0, "maximum": 100},
				"description": "Maximum supported staged batches to apply in this request.",
			}, map[string]any{
				"name":        "skip_verify",
				"in":          "query",
				"required":    false,
				"schema":      map[string]any{"type": "boolean"},
				"description": "Skip full staging/mapping verification after this apply request.",
			})...), response(200, "Import apply report returned")),
		},
		"/api/ext/v1/imports/{import_id}/verify": map[string]any{
			"get": operation("verifyImport", "Verify staged, applied, failed, and mapped import objects for one job.", auth(), nil, pathParams(
				param("import_id", "integer"),
			), response(200, "Import verification report returned")),
		},
		"/api/ext/v1/imports/{import_id}/batches": map[string]any{
			"post": operation("createImportBatch", "Upload one idempotent import batch produced by ags-migrate.", auth(), jsonBody(true, map[string]any{
				"batch_id":       stringSchema("Client-stable idempotency key for this batch."),
				"object_type":    stringSchema("Source object family such as labels, milestones, issues, issue_comments, pull_requests, pull_request_reviews, pull_request_review_comments, releases, release_assets, branch_protections, webhooks, workflows, or secret_names."),
				"schema_version": map[string]any{"type": "integer", "minimum": 1},
				"source_cursor":  stringSchema("Optional source cursor or range represented by this batch."),
				"checksum":       stringSchema("Optional sha256 checksum of newline-delimited item payloads. When omitted, AGS computes and stores the checksum from uploaded payloads."),
				"items": map[string]any{
					"type": "array",
					"items": map[string]any{
						"type": "object",
						"properties": map[string]any{
							"source_id":      stringSchema("Source object numeric or stable id."),
							"source_node_id": stringSchema("Source GraphQL node id when available."),
							"source_number":  map[string]any{"type": "integer"},
							"source_url":     stringSchema("Original source URL."),
							"payload":        map[string]any{"type": "object", "additionalProperties": true},
						},
						"required": []string{"payload"},
					},
				},
			}, []string{"batch_id", "object_type", "items"}), pathParams(
				param("import_id", "integer"),
			), response(201, "Import batch staged")),
			"get": operation("listImportBatches", "List staged/applied import batches for one job.", auth(), nil, append(pathParams(
				param("import_id", "integer"),
			), queryParams(map[string]any{
				"limit": map[string]any{"type": "integer", "minimum": 1, "maximum": 500, "description": "Maximum number of batches to return."},
			})...), response(200, "Import batches returned")),
		},
		"/api/ext/v1/imports/{import_id}/release-assets/{source_id}": map[string]any{
			"put": operation("uploadImportReleaseAsset", "Stage one release asset as raw bytes for a later release-asset batch. The body is not JSON.", auth(), nil, append(pathParams(
				param("import_id", "integer"), param("source_id", "string"),
			), queryParams(map[string]any{
				"name": "X-Import-Asset-Size", "in": "header", "required": true,
				"schema": map[string]any{"type": "integer", "minimum": 0},
			}, map[string]any{
				"name": "X-Import-Asset-Checksum", "in": "header", "required": true,
				"schema": stringSchema("SHA-256 checksum, with or without the sha256: prefix."),
			})...), response(201, "Release asset staged")),
		},
		"/api/ext/v1/imports/{import_id}/logs": map[string]any{
			"get": operation("listImportLogs", "List logs for one import job.", auth(), nil, append(pathParams(
				param("import_id", "integer"),
			), queryParams(map[string]any{
				"limit": map[string]any{"type": "integer", "minimum": 1, "maximum": 500, "description": "Maximum number of logs to return."},
			})...), response(200, "Import logs returned")),
		},
		"/api/ext/v1/imports/{import_id}/mapping": map[string]any{
			"get": operation("listImportMappings", "List source-to-target mappings for one import job.", auth(), nil, append(pathParams(
				param("import_id", "integer"),
			), queryParams(map[string]any{
				"limit": map[string]any{"type": "integer", "minimum": 1, "maximum": 500, "description": "Maximum number of mappings to return."},
			}, map[string]any{
				"name": "after_id", "in": "query", "required": false,
				"schema": map[string]any{"type": "integer", "minimum": 1}, "description": "Return mappings after this mapping ID. Results are ordered by mapping ID.",
			}, map[string]any{
				"name": "target_type", "in": "query", "required": false,
				"schema": map[string]any{"type": "string"}, "description": "Optional target mapping type filter, such as wiki_repository or access_handoff.",
			})...), response(200, "Import mappings returned")),
		},
		"/api/ext/v1/imports/{import_id}/source-users": map[string]any{
			"get": operation("listImportSourceUsers", "List historical source identities for one import job. Results use an ID cursor for large migrations.", auth(), nil, append(pathParams(
				param("import_id", "integer"),
			), queryParams(map[string]any{
				"limit": map[string]any{"type": "integer", "minimum": 1, "maximum": 500, "description": "Maximum number of source identities to return."},
			}, map[string]any{
				"name": "after_id", "in": "query", "required": false,
				"schema": map[string]any{"type": "integer", "minimum": 1}, "description": "Return records after this source-identity ID.",
			})...), response(200, "Imported source identities returned")),
			"patch": operation("mapImportSourceUsers", "Create attribution invitations in bulk. Mapping historical authors does not grant repository access.", auth(), jsonBody(true, map[string]any{
				"mappings": map[string]any{"type": "array", "minItems": 1, "maxItems": 500, "items": map[string]any{
					"type": "object", "properties": map[string]any{
						"source_user_id":    map[string]any{"type": "integer", "minimum": 1},
						"target_user_login": stringSchema("Existing active AGS organization member."),
						"skip_invitation":   map[string]any{"type": "boolean", "description": "Managed-identity exception; immediately reattributes history."},
					}, "required": []string{"source_user_id", "target_user_login"},
				}},
			}, []string{"mappings"}), pathParams(param("import_id", "integer")), response(200, "Source identity mappings updated")),
		},
		"/api/ext/v1/imports/{import_id}/source-users/{source_user_id}/claim": map[string]any{
			"post": operation("claimImportSourceUser", "Claim a historical identity when the authenticated AGS user already owns the matching immutable GitHub identity.", auth(), nil, pathParams(
				param("import_id", "integer"), param("source_user_id", "integer"),
			), response(200, "Historical identity claimed")),
		},
		"/api/ext/v1/imports/{import_id}/source-users/{source_user_id}/invitation": map[string]any{
			"post": operation("respondImportAttributionInvitation", "Accept or reject an attribution invitation for historical activity.", auth(), jsonBody(true, map[string]any{
				"accept": map[string]any{"type": "boolean"},
			}, []string{"accept"}), pathParams(
				param("import_id", "integer"), param("source_user_id", "integer"),
			), response(200, "Attribution invitation updated")),
		},
		"/api/ext/v1/user/import-source-users/pending": map[string]any{
			"get": operation("listMyClaimableImportSourceUsers", "List pending historical identities claimable by the authenticated user's verified GitHub identity.", auth(), nil, nil, response(200, "Claimable identities returned")),
		},
		"/api/ext/v1/user/import-attribution-invitations": map[string]any{
			"get": operation("listMyImportAttributionInvitations", "List attribution invitations awaiting the authenticated user's decision.", auth(), nil, nil, response(200, "Attribution invitations returned")),
		},
		"/api/ext/v1/user/orgs": map[string]any{
			"post": operation("createUserOrg", "Create a local organization for the authenticated user.", auth(), jsonBody(true, map[string]any{
				"login":                         stringSchema("Organization login."),
				"name":                          stringSchema("Optional organization display name."),
				"default_repository_permission": stringSchema("Default base repository permission for organization members."),
			}, []string{"login"}), nil, response(201, "Organization created")),
		},
		"/api/ext/v1/user/orgs/{org}/membership": map[string]any{
			"delete": operation("leaveUserOrg", "Remove the authenticated user's own membership from an organization.", auth(), nil, pathParams(
				param("org", "string"),
			), response(204, "Organization membership removed")),
		},
		"/api/ext/v1/user/tokens": map[string]any{
			"get": operation("listTokens", "List local user tokens.", auth(), nil, nil, response(200, "Tokens returned")),
			"post": operation("createToken", "Create a local user token.", auth(), jsonBody(true, map[string]any{
				"name":       stringSchema("Optional token display name."),
				"expires_at": map[string]any{"type": "string", "format": "date-time", "description": "Optional RFC3339 expiration timestamp."},
			}, nil), nil, response(201, "Token created")),
			"delete": operation("deleteToken", "Delete a local user token.", auth(), jsonBody(true, map[string]any{
				"id":       map[string]any{"type": "integer", "minimum": 1},
				"token_id": map[string]any{"type": "integer", "minimum": 1},
				"token":    stringSchema("Raw token value when deleting by value instead of numeric id."),
			}, nil), nil, response(204, "Token deleted")),
		},
		"/api/ext/v1/notifications/summary": map[string]any{
			"get": operation("getNotificationsSummary", "Get the authenticated viewer's notification summary.", auth(), nil, nil, response(200, "Notifications summary returned")),
		},
		"/api/ext/v1/orgs/{org}/management-summary": map[string]any{
			"get": operation("getOrgManagementSummary", "Get a management summary for an organization.", auth(), nil, pathParams(
				param("org", "string"),
			), response(200, "Organization management summary returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/summary": map[string]any{
			"get": operation("getRepoSummary", "Get a console summary for a repository.", auth(), nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(200, "Repository summary returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/issues/{number}/thread": map[string]any{
			"get": operation("getIssueThread", "Get an issue thread aggregate.", auth(), nil, append(pathParams(
				param("owner", "string"),
				param("repo", "string"),
				param("number", "integer"),
			), queryParams(
				param("include", "string"),
				param("comments_page", "integer"),
				param("comments_per_page", "integer"),
			)...), response(200, "Issue thread aggregate returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/team-sharing/enable": map[string]any{
			"post": operation("enableRepoTeamSharing", "Enable team-based sharing for a repository.", auth(), nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(200, "Team sharing enabled")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/pages": map[string]any{
			"get": operation("listWikiPages", "List wiki pages for a repository.", nil, nil, append(pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), queryParams(
				param("path", "string"),
				param("recursive", "boolean"),
				param("label", "string"),
				param("labels", "string"),
				param("exclude_label", "string"),
				param("exclude_labels", "string"),
			)...), response(200, "Wiki pages returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/search": map[string]any{
			"get": operation("searchWikiPages", "Search wiki pages for a repository, with optional label filters.", nil, nil, append(pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), queryParams(
				param("q", "string"),
				param("limit", "integer"),
				param("offset", "integer"),
				param("label", "string"),
				param("labels", "string"),
				param("exclude_label", "string"),
				param("exclude_labels", "string"),
			)...), response(200, "Wiki search results returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/catalog": map[string]any{
			"get": operation("getWikiCatalog", "Get an aggregate wiki catalog for a repository.", nil, nil, append(pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), queryParams(
				param("include", "string"),
				param("path", "string"),
				param("recursive", "boolean"),
				param("labels", "string"),
				param("exclude_labels", "string"),
			)...), response(200, "Wiki catalog returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/pages/batch": map[string]any{
			"post": operation("batchGetWikiPages", "Get multiple wiki pages in one extension aggregate request.", auth(), jsonBody(true, map[string]any{
				"slugs":      map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "Wiki page slugs to fetch."},
				"include":    map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "Optional fields to include, such as body, labels, backlinks, or backlink_count."},
				"body_limit": map[string]any{"type": "integer", "minimum": 0, "description": "Maximum body characters to return per page."},
				"ref":        stringSchema("Optional full commit SHA from wiki history."),
			}, []string{"slugs"}), pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(200, "Wiki page batch returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/tree": map[string]any{
			"get": operation("listWikiTree", "List one directory view from the authoritative wiki tree.", nil, nil, append(pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), queryParams(
				param("path", "string"),
				param("ref", "string"),
			)...), response(200, "Wiki tree returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/state": map[string]any{
			"get": operation("getWikiState", "Get the authoritative wiki derived-index state for a repository.", auth(), nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(200, "Current wiki state")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/reconcile/request": map[string]any{
			"post": operation("requestWikiReconcile", "Request a wiki reconcile without running it synchronously.", auth(), nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(202, "Reconcile request recorded")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/reconcile": map[string]any{
			"post": operation("reconcileWiki", "Run the authoritative wiki reconcile synchronously and return the persisted result.", auth(), nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(200, "Reconcile completed")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/move": map[string]any{
			"post": operation("moveWikiPagePrefix", "Atomically move all wiki pages under one slug prefix to another prefix.", auth(), jsonBody(true, map[string]any{
				"from":     stringSchema("Source wiki slug prefix to move."),
				"to":       stringSchema("Destination wiki slug prefix."),
				"message":  stringSchema("Optional commit message recorded for the wiki move."),
				"if_match": mapSchema("Latest blob SHAs keyed by source wiki slug."),
			}, []string{"from", "to", "if_match"}), pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(200, "Wiki pages moved")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/pages/{slug}": map[string]any{
			"get": operation("getWikiPage", "Get a wiki page by slug, optionally at a full commit SHA from that page's history.", nil, nil, append(pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), queryParams(
				map[string]any{
					"name":        "ref",
					"in":          "query",
					"schema":      map[string]any{"type": "string", "pattern": "^[0-9a-fA-F]{40}$"},
					"description": "Optional full commit SHA from the requested page's history.",
				},
			)...), response(200, "Wiki page returned")),
			"put": operation("putWikiPage", "Create or replace a wiki page by slug.", auth(), jsonBody(true, map[string]any{
				"body":    stringSchema("Markdown body for the wiki page."),
				"message": stringSchema("Optional commit message recorded for the wiki update."),
				"sha":     stringSchema("Optional blob SHA precondition for optimistic concurrency control."),
			}, []string{"body"}), pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), response(200, "Wiki page written")),
			"delete": operation("deleteWikiPage", "Delete a wiki page by slug.", auth(), jsonBody(false, map[string]any{
				"message": stringSchema("Optional commit message recorded for the wiki deletion."),
			}, nil), pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), response(204, "Wiki page deleted")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/pages/{slug}/move": map[string]any{
			"post": operation("moveWikiPage", "Move a wiki page to a new slug and rewrite eligible inbound wiki references in the same commit.", auth(), jsonBody(true, map[string]any{
				"new_slug": stringSchema("Destination slug for the wiki page."),
				"message":  stringSchema("Optional commit message recorded for the wiki move."),
				"if_match": stringSchema("Latest source page commit SHA observed by the client."),
			}, []string{"new_slug", "if_match"}), pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), response(200, "Wiki page moved and inbound references rewritten")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/pages/{slug}/labels": map[string]any{
			"get": operation("listWikiPageLabels", "List labels attached to a wiki page.", nil, nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), response(200, "Wiki page labels returned")),
			"post": operation("addWikiPageLabels", "Add labels to a wiki page.", auth(), jsonBody(true, map[string]any{
				"labels": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "Repository label names to attach."},
			}, []string{"labels"}), pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), response(200, "Wiki page labels returned")),
			"put": operation("setWikiPageLabels", "Replace labels attached to a wiki page.", auth(), jsonBody(true, map[string]any{
				"labels": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "Repository label names that should remain attached."},
			}, []string{"labels"}), pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), response(200, "Wiki page labels returned")),
			"delete": operation("removeAllWikiPageLabels", "Remove all labels from a wiki page.", auth(), nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), response(204, "Wiki page labels removed")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/pages/{slug}/labels/{name}": map[string]any{
			"delete": operation("removeWikiPageLabel", "Remove one label from a wiki page.", auth(), nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
				param("name", "string"),
			), response(200, "Remaining wiki page labels returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/pages/{slug}/history": map[string]any{
			"get": operation("listWikiPageHistory", "List paginated revision history for a wiki page slug.", nil, nil, append(pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), queryParams(
				param("page", "integer"),
				param("per_page", "integer"),
			)...), response(200, "Wiki page history returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/compact": map[string]any{
			"post": operation("compactWikiHistory", "Temporarily disabled while the wiki catalog corruption incident is being contained and repaired.", auth(), jsonBody(false, map[string]any{
				"before": stringSchema("Reserved for future bounded compaction support. Currently rejected when non-empty."),
			}, nil), pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(409, "Wiki history compaction is temporarily disabled")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/compact/{jobID}": map[string]any{
			"get": operation("getWikiCompactionJob", "Get the current status for an async wiki history compaction job.", auth(), nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
				param("jobID", "string"),
			), response(200, "Wiki history compaction job returned")),
		},
		"/api/ext/v1/admin/wiki/repos/{owner}/{repo}/repair-locks": map[string]any{
			"post": operation("repairWikiLocks", "Inspect and clear stale wiki branch lock files for one repository.", auth(), jsonBody(false, map[string]any{
				"force": map[string]any{
					"type":        "boolean",
					"description": "When true, clear the lock even if it is still fresh.",
				},
			}, nil), pathParams(
				param("owner", "string"),
				param("repo", "string"),
			), response(200, "Wiki lock repair result returned")),
		},
		"/api/ext/v1/repos/{owner}/{repo}/wiki/pages/{slug}/backlinks": map[string]any{
			"get": operation("listWikiBacklinks", "List inbound wiki links for a page slug.", nil, nil, pathParams(
				param("owner", "string"),
				param("repo", "string"),
				wikiSlugParamSpec(),
			), response(200, "Wiki backlinks returned")),
		},
	}
}

func operation(id, summary string, security []map[string][]string, requestBody map[string]any, params []map[string]any, responses map[string]any) map[string]any {
	op := map[string]any{
		"operationId": id,
		"summary":     summary,
		"responses":   responses,
	}
	if len(security) > 0 {
		op["security"] = security
	}
	if requestBody != nil {
		op["requestBody"] = requestBody
	}
	if len(params) > 0 {
		op["parameters"] = params
	}
	return op
}

func auth() []map[string][]string {
	return []map[string][]string{{"tokenAuth": []string{}}}
}

func pathParams(params ...map[string]any) []map[string]any {
	for _, param := range params {
		param["in"] = "path"
		param["required"] = true
	}
	return params
}

func queryParams(params ...map[string]any) []map[string]any {
	for _, param := range params {
		param["in"] = "query"
	}
	return params
}

func param(name, typ string) map[string]any {
	schema := map[string]any{"type": typ}
	if typ == "integer" {
		schema["minimum"] = 1
	}
	return map[string]any{
		"name":   name,
		"schema": schema,
	}
}

func wikiSlugParamSpec() map[string]any {
	p := param("slug", "string")
	p["description"] = "Wiki page slug as one path parameter. Encode nested slug separators as %2F, for example guides%2Fsetup."
	return p
}

func stringSchema(description string) map[string]any {
	return map[string]any{
		"type":        "string",
		"description": description,
	}
}

func mapSchema(description string) map[string]any {
	return map[string]any{
		"type":                 "object",
		"description":          description,
		"additionalProperties": map[string]any{"type": "string"},
	}
}

func schemaRef(name string) map[string]any {
	return map[string]any{"$ref": "#/components/schemas/" + name}
}

func objectSchema(required []string, properties map[string]any) map[string]any {
	result := map[string]any{"type": "object", "properties": properties}
	if len(required) > 0 {
		result["required"] = required
	}
	return result
}

func githubStackOpenAPISchemas() map[string]any {
	stackRepo := objectSchema([]string{"id", "url", "name"}, map[string]any{
		"id": map[string]any{"type": "integer", "format": "int64"}, "url": map[string]any{"type": "string"}, "name": map[string]any{"type": "string"},
	})
	stackHeadBase := objectSchema([]string{"ref", "sha", "repo"}, map[string]any{
		"ref": map[string]any{"type": "string"}, "sha": map[string]any{"type": "string"}, "repo": stackRepo,
	})
	stackUser := objectSchema([]string{
		"avatar_url", "events_url", "followers_url", "following_url", "gists_url", "gravatar_id", "html_url", "id", "node_id", "login",
		"organizations_url", "received_events_url", "repos_url", "site_admin", "starred_url", "subscriptions_url", "type", "url",
	}, map[string]any{
		"login": map[string]any{"type": "string"}, "id": map[string]any{"type": "integer", "format": "int64"}, "node_id": map[string]any{"type": "string"},
		"avatar_url": map[string]any{"type": "string"}, "gravatar_id": map[string]any{"type": "string", "nullable": true}, "url": map[string]any{"type": "string"},
		"html_url": map[string]any{"type": "string"}, "followers_url": map[string]any{"type": "string"}, "following_url": map[string]any{"type": "string"},
		"gists_url": map[string]any{"type": "string"}, "starred_url": map[string]any{"type": "string"}, "subscriptions_url": map[string]any{"type": "string"},
		"organizations_url": map[string]any{"type": "string"}, "repos_url": map[string]any{"type": "string"}, "events_url": map[string]any{"type": "string"},
		"received_events_url": map[string]any{"type": "string"}, "type": map[string]any{"type": "string"}, "site_admin": map[string]any{"type": "boolean"},
		"user_view_type": map[string]any{"type": "string"},
	})
	stackUser["nullable"] = true
	minimalPR := objectSchema([]string{"number", "state", "draft", "merged_at", "head"}, map[string]any{
		"number": map[string]any{"type": "integer"}, "state": map[string]any{"type": "string", "enum": []string{"open", "closed"}},
		"draft": map[string]any{"type": "boolean"}, "merged_at": map[string]any{"type": "string", "format": "date-time", "nullable": true},
		"head": objectSchema([]string{"ref", "sha"}, map[string]any{"ref": map[string]any{"type": "string"}, "sha": map[string]any{"type": "string"}}),
	})
	fullPR := objectSchema([]string{"id", "number", "url", "head", "base", "node_id", "title", "state", "merged_at", "draft", "html_url", "user"}, map[string]any{
		"id": map[string]any{"type": "integer", "format": "int64"}, "number": map[string]any{"type": "integer"}, "url": map[string]any{"type": "string"},
		"head": stackHeadBase, "base": stackHeadBase, "node_id": map[string]any{"type": "string"}, "title": map[string]any{"type": "string"},
		"state": map[string]any{"type": "string", "enum": []string{"open", "closed"}}, "merged_at": map[string]any{"type": "string", "format": "date-time", "nullable": true},
		"draft": map[string]any{"type": "boolean"}, "html_url": map[string]any{"type": "string", "format": "uri"}, "user": stackUser,
	})
	stackProperties := func(pullRequestSchema map[string]any) map[string]any {
		return map[string]any{
			"id": map[string]any{"type": "integer"}, "number": map[string]any{"type": "integer"}, "node_id": map[string]any{"type": "string"},
			"url":  map[string]any{"type": "string", "format": "uri"},
			"base": objectSchema([]string{"ref"}, map[string]any{"ref": map[string]any{"type": "string"}}), "open": map[string]any{"type": "boolean"},
			"created_at": map[string]any{"type": "string", "format": "date-time"}, "pull_requests": map[string]any{"type": "array", "items": pullRequestSchema},
		}
	}
	stackRequired := []string{"id", "number", "node_id", "url", "base", "open", "created_at", "pull_requests"}
	asyncVariant := func(status string, details map[string]any) map[string]any {
		return objectSchema([]string{"status", "details"}, map[string]any{
			"status": map[string]any{"type": "string", "enum": []string{status}}, "details": details,
		})
	}
	messageDetails := objectSchema([]string{"message"}, map[string]any{"message": map[string]any{"type": "string"}})
	return map[string]any{
		"PullRequestStackMinimal": objectSchema(stackRequired, stackProperties(minimalPR)),
		"PullRequestStack":        objectSchema(stackRequired, stackProperties(fullPR)),
		"PullRequestAsyncMergeResult": map[string]any{"oneOf": []any{
			asyncVariant("pending", objectSchema([]string{"message", "uuid", "merge_method", "merge_action", "expected_head_sha"}, map[string]any{
				"message": map[string]any{"type": "string"}, "uuid": map[string]any{"type": "string"},
				"merge_method":      map[string]any{"type": "string", "enum": []string{"merge", "squash", "rebase"}},
				"merge_action":      map[string]any{"type": "string", "enum": []string{"default", "direct_merge", "merge_queue"}},
				"expected_head_sha": map[string]any{"type": "string"},
			})),
			asyncVariant("merged", objectSchema([]string{"message", "sha"}, map[string]any{"message": map[string]any{"type": "string"}, "sha": map[string]any{"type": "string"}})),
			asyncVariant("enqueued", messageDetails), asyncVariant("failed", messageDetails),
		}},
	}
}

func jsonBody(required bool, properties map[string]any, requiredProps []string) map[string]any {
	schema := map[string]any{
		"type":       "object",
		"properties": properties,
	}
	if len(requiredProps) > 0 {
		schema["required"] = requiredProps
	}
	return map[string]any{
		"required": required,
		"content": map[string]any{
			"application/json": map[string]any{
				"schema": schema,
			},
		},
	}
}

func jsonAndFormBody(required bool, properties map[string]any, requiredProps []string) map[string]any {
	schema := map[string]any{
		"type":       "object",
		"properties": properties,
	}
	if len(requiredProps) > 0 {
		schema["required"] = requiredProps
	}
	return map[string]any{
		"required": required,
		"content": map[string]any{
			"application/json": map[string]any{
				"schema": schema,
			},
			"application/x-www-form-urlencoded": map[string]any{
				"schema": schema,
			},
		},
	}
}

func multipartFileBody() map[string]any {
	return map[string]any{
		"required": true,
		"content": map[string]any{
			"multipart/form-data": map[string]any{
				"schema": objectSchema([]string{"file"}, map[string]any{
					"file": map[string]any{"type": "string", "format": "binary"},
				}),
			},
		},
	}
}

func response(code int, description string) map[string]any {
	return map[string]any{
		strconv.Itoa(code): map[string]any{
			"description": description,
		},
	}
}

func jsonResponse(code int, description string, schema map[string]any) map[string]any {
	return map[string]any{
		strconv.Itoa(code): map[string]any{
			"description": description,
			"content":     map[string]any{"application/json": map[string]any{"schema": schema}},
		},
	}
}

func responses(parts ...map[string]any) map[string]any {
	out := make(map[string]any, len(parts))
	for _, part := range parts {
		for code, value := range part {
			out[code] = value
		}
	}
	return out
}
