package kb

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/service/resource/activity"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/repos"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
	kbservice "github.com/tidbcloud/AgentTeam/internal/service/kb"
	storekb "github.com/tidbcloud/AgentTeam/internal/store/kb"
)

const (
	maxPerPage         = 100
	batchAgentIDsLimit = 200
)

// Service is the KB service surface required by the HTTP handler.
type Service interface {
	ListKnowledgeBases(ctx context.Context, workspaceID, subject, view, query string, tagIDs []string, page, perPage int) (kbservice.KnowledgeBaseCollectionPage, error)
	ListKnowledgeBaseTaxonomy(ctx context.Context, workspaceID, subject string) (kbservice.KnowledgeBaseTaxonomy, error)
	CreateKnowledgeBaseGroup(ctx context.Context, workspaceID, subject string, input kbservice.KnowledgeBaseTaxonomyMutation) (storekb.KnowledgeBaseGroup, error)
	UpdateKnowledgeBaseGroup(ctx context.Context, workspaceID, subject, groupID string, input kbservice.KnowledgeBaseTaxonomyMutation) (storekb.KnowledgeBaseGroup, error)
	DeleteKnowledgeBaseGroup(ctx context.Context, workspaceID, subject, groupID string) error
	CreateKnowledgeBaseTag(ctx context.Context, workspaceID, subject string, input kbservice.KnowledgeBaseTaxonomyMutation) (storekb.KnowledgeBaseTag, error)
	UpdateKnowledgeBaseTag(ctx context.Context, workspaceID, subject, tagID string, input kbservice.KnowledgeBaseTaxonomyMutation) (storekb.KnowledgeBaseTag, error)
	DeleteKnowledgeBaseTag(ctx context.Context, workspaceID, subject, tagID string) error
	ListKnowledgeBaseActivity(ctx context.Context, workspaceID, subject string, opts activity.ResourceActivityListOptions) (activity.ResourceActivityPage, error)
	ListAgentAccessibleKnowledgeBases(ctx context.Context, workspaceID, subject, agentID string, page, perPage int) ([]kbservice.AgentAccessibleKnowledgeBase, error)
	BatchListAgentAccessibleKnowledgeBases(ctx context.Context, workspaceID, subject string, options kbservice.AgentKnowledgeBaseAccessBatchOptions) (kbservice.AgentKnowledgeBaseAccessBatch, error)
	SetAgentKnowledgeBaseAccess(ctx context.Context, workspaceID, subject, agentID string, input kbservice.AgentKnowledgeBaseBulkAccessMutation) (kbservice.AgentKnowledgeBaseBulkAccessResult, error)
	SetAgentKnowledgeBaseAccessForKnowledgeBase(ctx context.Context, workspaceID, subject, agentID, kbID, accessLevel string) (kbservice.AgentKnowledgeBaseGrantResult, error)
	CreateKnowledgeBase(ctx context.Context, workspaceID, subject string, input kbservice.KBMutation) (storekb.KnowledgeBase, error)
	CreateKnowledgeBaseSetup(ctx context.Context, workspaceID, subject string, input kbservice.KBMutation) (storekb.KnowledgeBase, kbservice.KBSetupView, error)
	ConvertKnowledgeBaseOfficialToDraft(ctx context.Context, workspaceID, subject, kbID string) (kbservice.KBSetupView, error)
	StartKnowledgeBaseOfficialEdit(ctx context.Context, workspaceID, subject, kbID string) (kbservice.KBSetupView, error)
	GetKnowledgeBaseSetup(ctx context.Context, workspaceID, subject, kbID string) (kbservice.KBSetupView, error)
	UpdateKnowledgeBaseOfficialEditBasic(ctx context.Context, workspaceID, subject, kbID string, input kbservice.KBOfficialEditBasicMutation) (kbservice.KBSetupView, error)
	SaveKnowledgeBaseOfficialEdit(ctx context.Context, workspaceID, subject, kbID string, input kbservice.KBOfficialEditSaveMutation) (storekb.KnowledgeBase, error)
	UpdateKnowledgeBaseSetupStep(ctx context.Context, workspaceID, subject, kbID, activeStep string, expectedVersion int64) (kbservice.KBSetupView, error)
	SetKnowledgeBaseSetupSourceArtifact(ctx context.Context, workspaceID, subject, kbID, sourceID, kind, state string) (kbservice.KBSetupView, error)
	RequestDirectWikiGeneration(ctx context.Context, workspaceID, subject, kbID, sourceID string, input kbservice.KBWikiGenerationMutation) (kbservice.KBSetupView, error)
	RequestWikiGenerationApproval(ctx context.Context, workspaceID, subject, kbID, sourceID string, input kbservice.KBWikiGenerationMutation) (kbservice.KBSetupView, error)
	DecideWikiGenerationApproval(ctx context.Context, workspaceID, subject, kbID, sourceID, approvalID string, expectedVersion int64, clientDecisionID, decision, reason string) (kbservice.KBSetupView, error)
	WithdrawWikiGenerationApproval(ctx context.Context, workspaceID, subject, kbID, sourceID, approvalID string, expectedVersion int64, clientRequestID string) (kbservice.KBSetupView, error)
	RequestPublicationApproval(ctx context.Context, workspaceID, subject, kbID, workingRevision, idempotencyKey string) (kbservice.KBSetupView, error)
	DecidePublicationApproval(ctx context.Context, workspaceID, subject, kbID, approvalID string, expectedVersion int64, clientDecisionID, decision, reason string) (kbservice.KBSetupView, error)
	WithdrawPublicationApproval(ctx context.Context, workspaceID, subject, kbID, approvalID string, expectedVersion int64, clientRequestID string) (kbservice.KBSetupView, error)
	GetPublicationReview(ctx context.Context, workspaceID, subject, approvalID string) (kbservice.KBPublicationReview, error)
	DecidePublicationReview(ctx context.Context, workspaceID, subject, approvalID string, expectedVersion int64, clientDecisionID, decision, reason string) (kbservice.KBPublicationReview, error)
	GetKnowledgeBase(ctx context.Context, workspaceID, subject, id string) (storekb.KnowledgeBase, error)
	UpdateKnowledgeBase(ctx context.Context, workspaceID, subject, id string, input kbservice.KBMutation) (storekb.KnowledgeBase, error)
	ArchiveKnowledgeBase(ctx context.Context, workspaceID, subject, id string) (storekb.KnowledgeBase, error)
	PinKnowledgeBase(ctx context.Context, workspaceID, subject, id string) (storekb.KnowledgeBasePin, error)
	UnpinKnowledgeBase(ctx context.Context, workspaceID, subject, id string) error
	ListKnowledgeBasePermissions(ctx context.Context, workspaceID, subject, kbID string) (kbservice.KBPermissionsSummary, error)
	UpsertKnowledgeBasePermission(ctx context.Context, workspaceID, subject, kbID string, input kbservice.KBPermissionGrantMutation) (kbservice.KBPermissionGrant, error)
	UpdateKnowledgeBasePermissionRepositoryOverrides(ctx context.Context, workspaceID, subject, kbID, grantID string, input []kbservice.KBPermissionRepositoryOverrideMutation) (kbservice.KBPermissionGrant, error)
	ReconcileKnowledgeBasePermissionRepositoryAccess(ctx context.Context, workspaceID, subject, kbID, grantID string) (kbservice.KBPermissionGrant, error)
	DeleteKnowledgeBasePermission(ctx context.Context, workspaceID, subject, kbID, grantID string) error
	GetKnowledgeBaseOverview(ctx context.Context, workspaceID, subject, kbID string) (kbservice.KBOverview, error)
	GetKnowledgeBaseSourceDetail(ctx context.Context, workspaceID, subject, kbID, sourceID string) (kbservice.KBSourceDetail, error)
	DescribeKnowledgeContext(ctx context.Context, workspaceID, subject string, request kbservice.KBRetrievalContextRequest) (kbservice.KBRetrievalContextResult, error)
	DescribeKnowledgeCapabilities(ctx context.Context, workspaceID, subject string, request kbservice.KBCapabilitiesRequest) (kbservice.KBCapabilitiesResult, error)
	SearchKnowledge(ctx context.Context, workspaceID, subject string, request kbservice.KBSearchRequest) (kbservice.KBSearchResult, error)
	ReadKnowledge(ctx context.Context, workspaceID, subject string, request kbservice.KBReadRequest) (kbservice.KBReadResult, error)
	ListKnowledgeTree(ctx context.Context, workspaceID, subject string, request kbservice.KBTreeRequest) (kbservice.KBTreeResult, error)
	BuildKnowledgeManifest(ctx context.Context, workspaceID, subject string, request kbservice.KBManifestRequest) (kbservice.KBManifestResult, error)
	StatKnowledge(ctx context.Context, workspaceID, subject string, request kbservice.KBStatRequest) (kbservice.KBStatResult, error)
	BatchReadKnowledge(ctx context.Context, workspaceID, subject string, request kbservice.KBBatchReadRequest) (kbservice.KBBatchReadResult, error)
	FetchKnowledge(ctx context.Context, workspaceID, subject string, request kbservice.KBFetchRequest) (kbservice.KBFetchResult, error)
	BatchKnowledgeTree(ctx context.Context, workspaceID, subject string, request kbservice.KBBatchTreeRequest) (kbservice.KBBatchTreeResult, error)
	ListSources(ctx context.Context, workspaceID, subject, kbID string, page, perPage int) ([]storekb.Source, error)
	ListRetrievableSources(ctx context.Context, workspaceID, subject, kbID string, page, perPage int) ([]storekb.Source, error)
	CreateSource(ctx context.Context, workspaceID, subject, kbID string, input kbservice.KBSourceMutation) (storekb.Source, error)
	UpdateSource(ctx context.Context, workspaceID, subject, kbID, sourceID string, input kbservice.KBSourceMutation) (storekb.Source, error)
	DeleteSource(ctx context.Context, workspaceID, subject, kbID, sourceID string) error
	UpdateSourceMirror(ctx context.Context, workspaceID, subject, kbID, sourceID string, input kbservice.KBSourceMirrorMutation) (storekb.SourceMirror, error)
	RequestSourceMirrorSync(ctx context.Context, workspaceID, subject, kbID, sourceID, ref string) (storekb.Job, error)
	ListSourceMirrorSyncs(ctx context.Context, workspaceID, subject, kbID, sourceID string, page, perPage int) (kbservice.SourceMirrorSyncListResult, error)
	ListAuditEvents(ctx context.Context, workspaceID, subject, kbID string, page, perPage int) ([]storekb.AuditEvent, error)
	ListKBSavedViews(ctx context.Context, workspaceID string, subject string, page int, perPage int) ([]storekb.KBSavedView, error)
	CreateKBSavedView(ctx context.Context, workspaceID string, subject string, input kbservice.KBSavedViewMutation) (storekb.KBSavedView, error)
	UpdateKBSavedView(ctx context.Context, workspaceID string, subject string, viewID string, input kbservice.KBSavedViewMutation) (storekb.KBSavedView, error)
	DeleteKBSavedView(ctx context.Context, workspaceID string, subject string, viewID string) error
	ListKBSavedViewKnowledgeBases(ctx context.Context, workspaceID string, subject string, viewID string, page int, perPage int) (kbservice.KnowledgeBaseCollectionPage, error)
}

type KBHandler struct {
	service   Service
	dashboard *DashboardHandler
}

type RouteMiddleware func(http.Handler) http.Handler

type routeRegistrar interface {
	Handle(pattern string, handler http.Handler)
}

type DisabledHandler struct{}

func (DisabledHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
	meshxapi.WriteError(w, meshxapi.NewHTTPError(http.StatusServiceUnavailable, "KB API is not configured"))
}

func NewKBHandler(service Service) *KBHandler {
	handler := &KBHandler{service: service}
	if dashboard, ok := service.(DashboardService); ok {
		handler.dashboard = &DashboardHandler{service: dashboard}
	}
	return handler
}

func RegisterRoutes(router routeRegistrar, handler *KBHandler, middleware RouteMiddleware) {
	if router == nil {
		return
	}
	if handler == nil {
		disabled := DisabledHandler{}
		registerKBRoute(router, "/api/v1/kb", disabled, middleware)
		registerKBRoute(router, "/api/v1/kb/", disabled, middleware)
		return
	}
	registerKBRoute(router, "GET /api/v1/kb", meshxapi.HandleAuth(handler.List, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/activity", meshxapi.HandleAuth(handler.ListActivity, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/agents/{agent_id}/accessible", meshxapi.HandleAuth(handler.ListAgentAccessible, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/agents/access:batch", meshxapi.HandleAuthJSON[dto.AgentKnowledgeBaseAccessBatchRequest](handler.AgentAccessBatch, kbErrorHandling()), middleware)
	registerKBRoute(router, "PUT /api/v1/kb/agents/{agent_id}/permissions", meshxapi.HandleAuthJSON[dto.AgentKnowledgeBaseBulkAccessRequest](handler.SetAgentPermissions, kbErrorHandling()), middleware)
	registerKBRoute(router, "PUT /api/v1/kb/agents/{agent_id}/knowledge-bases/{kb_id}", meshxapi.HandleAuthJSON[dto.AgentKnowledgeBaseAccessItemRequest](handler.SetAgentKnowledgeBaseAccess, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb", meshxapi.HandleAuthJSON[dto.KnowledgeBaseCreateRequest](handler.Create, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/setup-sessions", meshxapi.HandleAuthJSON[dto.KnowledgeBaseSetupCreateRequest](handler.CreateSetup, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/collection/taxonomy", meshxapi.HandleAuth(handler.ListTaxonomy, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/collection/groups", meshxapi.HandleAuthJSON[dto.KnowledgeBaseTaxonomyMutationRequest](handler.CreateGroup, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/collection/groups/{group_id}", meshxapi.HandleAuthJSON[dto.KnowledgeBaseTaxonomyMutationRequest](handler.UpdateGroup, kbErrorHandling()), middleware)
	registerKBRoute(router, "DELETE /api/v1/kb/collection/groups/{group_id}", meshxapi.HandleAuth(handler.DeleteGroup, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/collection/tags", meshxapi.HandleAuthJSON[dto.KnowledgeBaseTaxonomyMutationRequest](handler.CreateTag, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/collection/tags/{tag_id}", meshxapi.HandleAuthJSON[dto.KnowledgeBaseTaxonomyMutationRequest](handler.UpdateTag, kbErrorHandling()), middleware)
	registerKBRoute(router, "DELETE /api/v1/kb/collection/tags/{tag_id}", meshxapi.HandleAuth(handler.DeleteTag, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}", meshxapi.HandleAuth(handler.Get, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}/setup-session", meshxapi.HandleAuth(handler.GetSetup, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/draft-conversion", meshxapi.HandleAuth(handler.ConvertOfficialToDraft, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/official-edit-session", meshxapi.HandleAuth(handler.StartOfficialEdit, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/{kb_id}/setup-session/official-basic", meshxapi.HandleAuthJSON[dto.KnowledgeBaseOfficialEditBasicRequest](handler.UpdateOfficialEditBasic, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/setup-session/official-save", meshxapi.HandleAuthJSON[dto.KnowledgeBaseOfficialEditSaveRequest](handler.SaveOfficialEdit, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/{kb_id}/setup-session", meshxapi.HandleAuthJSON[dto.KnowledgeBaseSetupStepRequest](handler.UpdateSetupStep, kbErrorHandling()), middleware)
	registerKBRoute(router, "PUT /api/v1/kb/{kb_id}/setup-session/sources/{source_id}/{artifact_kind}", meshxapi.HandleAuthJSON[dto.KnowledgeBaseSetupSourceArtifactRequest](handler.SetSetupSourceArtifact, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/setup-session/sources/{source_id}/wiki/direct-generation", meshxapi.HandleAuthJSON[dto.KnowledgeBaseWikiGenerationRequest](handler.RequestDirectWikiGeneration, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/setup-session/sources/{source_id}/wiki/approval-requests", meshxapi.HandleAuthJSON[dto.KnowledgeBaseWikiGenerationRequest](handler.RequestWikiGenerationApproval, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/setup-session/sources/{source_id}/wiki/approval-requests/{approval_id}/decision", meshxapi.HandleAuthJSON[dto.KnowledgeBaseWikiGenerationDecisionRequest](handler.DecideWikiGenerationApproval, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/setup-session/sources/{source_id}/wiki/approval-requests/{approval_id}/withdraw", meshxapi.HandleAuthJSON[dto.KnowledgeBaseWikiGenerationWithdrawRequest](handler.WithdrawWikiGenerationApproval, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/setup-session/publication-requests", meshxapi.HandleAuthJSON[dto.KnowledgeBasePublicationRequest](handler.RequestPublicationApproval, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/setup-session/publication-requests/{approval_id}/decision", meshxapi.HandleAuthJSON[dto.KnowledgeBasePublicationDecisionRequest](handler.DecidePublicationApproval, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/setup-session/publication-requests/{approval_id}/withdraw", meshxapi.HandleAuthJSON[dto.KnowledgeBasePublicationWithdrawRequest](handler.WithdrawPublicationApproval, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb-publication-reviews/{approval_id}", meshxapi.HandleAuth(handler.GetPublicationReview, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb-publication-reviews/{approval_id}/decision", meshxapi.HandleAuthJSON[dto.KnowledgeBasePublicationDecisionRequest](handler.DecidePublicationReview, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/{kb_id}", meshxapi.HandleAuthJSON[dto.KnowledgeBaseUpdateRequest](handler.Update, kbErrorHandling()), middleware)
	registerKBRoute(router, "DELETE /api/v1/kb/{kb_id}", meshxapi.HandleAuth(handler.Archive, kbErrorHandling()), middleware)
	registerKBRoute(router, "PUT /api/v1/kb/{kb_id}/collection-pin", meshxapi.HandleAuth(handler.Pin, kbErrorHandling()), middleware)
	registerKBRoute(router, "DELETE /api/v1/kb/{kb_id}/collection-pin", meshxapi.HandleAuth(handler.Unpin, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}/permissions", meshxapi.HandleAuth(handler.ListPermissions, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/permissions/grants", meshxapi.HandleAuthJSON[dto.KBPermissionGrantRequest](handler.UpsertPermission, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/{kb_id}/permissions/grants/{grant_id}", meshxapi.HandleAuthJSON[dto.KBPermissionGrantRequest](handler.UpsertPermission, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/{kb_id}/permissions/grants/{grant_id}/repository-overrides", meshxapi.HandleAuthJSON[dto.KBPermissionRepositoryOverridesRequest](handler.UpdatePermissionRepositoryOverrides, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/permissions/grants/{grant_id}/repository-access/reconcile", meshxapi.HandleAuth(handler.ReconcilePermissionRepositoryAccess, kbErrorHandling()), middleware)
	registerKBRoute(router, "DELETE /api/v1/kb/{kb_id}/permissions/grants/{grant_id}", meshxapi.HandleAuth(handler.DeletePermission, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}/overview", meshxapi.HandleAuth(handler.Overview, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/context", meshxapi.HandleAuthJSON[dto.KBRetrievalContextRequest](handler.RetrievalContext, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/search", meshxapi.HandleAuthJSON[dto.KBRetrievalSearchRequest](handler.RetrievalSearch, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/read", meshxapi.HandleAuthJSON[dto.KBRetrievalReadRequest](handler.RetrievalRead, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/fetch", meshxapi.HandleAuthJSON[dto.KBRetrievalFetchRequest](handler.RetrievalFetch, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/tree", meshxapi.HandleAuthJSON[dto.KBRetrievalTreeRequest](handler.RetrievalTree, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/capabilities", meshxapi.HandleAuthJSON[dto.KBCapabilitiesRequest](handler.RetrievalCapabilities, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/manifest", meshxapi.HandleAuthJSON[dto.KBManifestRequest](handler.RetrievalManifest, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/stat", meshxapi.HandleAuthJSON[dto.KBStatRequest](handler.RetrievalStat, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/batch-read", meshxapi.HandleAuthJSON[dto.KBBatchReadRequest](handler.RetrievalBatchRead, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/batch-tree", meshxapi.HandleAuthJSON[dto.KBBatchTreeRequest](handler.RetrievalBatchTree, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/retrieval/knowledge/{operation}", meshxapi.HandleAuth(handler.KBKnowledge, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}/retrieval-sources", meshxapi.HandleAuth(handler.ListRetrievableSources, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}/sources", meshxapi.HandleAuth(handler.ListSources, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}/sources/{source_id}/detail", meshxapi.HandleAuth(handler.SourceDetail, kbErrorHandling()), middleware)
	if handler.dashboard != nil {
		registerKBRoute(router, "GET /api/v1/kb/{kb_id}/sources/{source_id}/dashboard", meshxapi.HandleAuth(handler.dashboard.Status, kbErrorHandling()), middleware)
		registerKBRoute(router, "GET /api/v1/kb/{kb_id}/sources/{source_id}/dashboard/source-preview", meshxapi.HandleAuth(handler.dashboard.SourcePreview, kbErrorHandling()), middleware)
		registerKBRoute(router, "POST /api/v1/kb/{kb_id}/sources/{source_id}/dashboard/import", handler.dashboard.importHTTP(), middleware)
		registerKBRoute(router, "GET /api/v1/kb/{kb_id}/sources/{source_id}/dashboard/files/{path}", handler.dashboard.fileHTTP(), middleware)
	}
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/sources", meshxapi.HandleAuthJSON[dto.KBSourceMutationRequest](handler.CreateSource, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/{kb_id}/sources/{source_id}", meshxapi.HandleAuthJSON[dto.KBSourceMutationRequest](handler.UpdateSource, kbErrorHandling()), middleware)
	registerKBRoute(router, "DELETE /api/v1/kb/{kb_id}/sources/{source_id}", meshxapi.HandleAuth(handler.DeleteSource, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/{kb_id}/sources/{source_id}/mirror", meshxapi.HandleAuthJSON[dto.KBSourceMirrorMutationRequest](handler.UpdateSourceMirror, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/{kb_id}/sources/{source_id}/mirror-syncs", meshxapi.HandleAuthJSON[dto.KBSourceMirrorSyncRequest](handler.RequestSourceMirrorSync, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}/sources/{source_id}/mirror-syncs", meshxapi.HandleAuth(handler.ListSourceMirrorSyncs, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/{kb_id}/audit-events", meshxapi.HandleAuth(handler.ListAuditEvents, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/collection/saved-views", meshxapi.HandleAuth(handler.ListSavedViews, kbErrorHandling()), middleware)
	registerKBRoute(router, "POST /api/v1/kb/collection/saved-views", meshxapi.HandleAuthJSON[dto.KBSavedViewMutationRequest](handler.CreateSavedView, kbErrorHandling()), middleware)
	registerKBRoute(router, "PATCH /api/v1/kb/collection/saved-views/{view_id}", meshxapi.HandleAuthJSON[dto.KBSavedViewMutationRequest](handler.UpdateSavedView, kbErrorHandling()), middleware)
	registerKBRoute(router, "DELETE /api/v1/kb/collection/saved-views/{view_id}", meshxapi.HandleAuth(handler.DeleteSavedView, kbErrorHandling()), middleware)
	registerKBRoute(router, "GET /api/v1/kb/collection/saved-views/{view_id}/knowledge-bases", meshxapi.HandleAuth(handler.ListSavedViewKnowledgeBases, kbErrorHandling()), middleware)
}

func registerKBRoute(router routeRegistrar, pattern string, handler http.Handler, middleware RouteMiddleware) {
	if middleware != nil {
		handler = middleware(handler)
	}
	router.Handle(pattern, handler)
}

func (h *KBHandler) ensureService() error {
	if h == nil || h.service == nil {
		return meshxapi.NewHTTPError(http.StatusServiceUnavailable, "KB API is not configured")
	}
	return nil
}

func (h *KBHandler) List(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	result, err := h.service.ListKnowledgeBases(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.URL.Query().Get("view"), r.Request.URL.Query().Get("q"), r.Request.URL.Query()["tag"], page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseListResponse(result.Items, result.GroupCounts, result.Page, result.PerPage, result.Total, result.HasMore)), nil
}

func (h *KBHandler) ListTaxonomy(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	taxonomy, err := h.service.ListKnowledgeBaseTaxonomy(r.Context(), r.WorkspaceID(), r.Subject())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseTaxonomy(taxonomy)), nil
}

func (h *KBHandler) CreateGroup(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseTaxonomyMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	group, err := h.service.CreateKnowledgeBaseGroup(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToKnowledgeBaseGroup(group)), nil
}

func (h *KBHandler) UpdateGroup(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseTaxonomyMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	group, err := h.service.UpdateKnowledgeBaseGroup(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("group_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseGroup(group)), nil
}

func (h *KBHandler) DeleteGroup(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if err := h.service.DeleteKnowledgeBaseGroup(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("group_id")); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *KBHandler) CreateTag(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseTaxonomyMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	tag, err := h.service.CreateKnowledgeBaseTag(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToKnowledgeBaseTag(tag)), nil
}

func (h *KBHandler) UpdateTag(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseTaxonomyMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	tag, err := h.service.UpdateKnowledgeBaseTag(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("tag_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseTag(tag)), nil
}

func (h *KBHandler) DeleteTag(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if err := h.service.DeleteKnowledgeBaseTag(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("tag_id")); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *KBHandler) ListActivity(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	opts, err := activityListOptions(r.Request)
	if err != nil {
		return meshxapi.Response{}, err
	}
	page, err := h.service.ListKnowledgeBaseActivity(r.Context(), r.WorkspaceID(), r.Subject(), opts)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToResourceActivityListResponse(page)), nil
}

func (h *KBHandler) ListAgentAccessible(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	items, err := h.service.ListAgentAccessibleKnowledgeBases(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("agent_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAgentAccessibleKnowledgeBases(items)), nil
}

func (h *KBHandler) AgentAccessBatch(r meshxapi.AuthenticatedJSON[dto.AgentKnowledgeBaseAccessBatchRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if len(r.Body.AgentIDs) == 0 {
		return meshxapi.OK(dto.AgentKnowledgeBaseAccessBatchResponse{Agents: map[string][]dto.AgentAccessibleKnowledgeBaseForSync{}}), nil
	}
	if len(r.Body.AgentIDs) > batchAgentIDsLimit {
		return meshxapi.Response{}, meshxapi.NewHTTPError(http.StatusBadRequest, "too many agentIds in batch request")
	}
	batch, err := h.service.BatchListAgentAccessibleKnowledgeBases(r.Context(), r.WorkspaceID(), r.Subject(), kbservice.AgentKnowledgeBaseAccessBatchOptions{AgentIDs: r.Body.AgentIDs})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAgentKnowledgeBaseAccessBatchResponse(batch)), nil
}

func (h *KBHandler) SetAgentPermissions(r meshxapi.AuthenticatedJSON[dto.AgentKnowledgeBaseBulkAccessRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.SetAgentKnowledgeBaseAccess(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("agent_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAgentKnowledgeBaseBulkAccessResponse(result)), nil
}

func (h *KBHandler) SetAgentKnowledgeBaseAccess(r meshxapi.AuthenticatedJSON[dto.AgentKnowledgeBaseAccessItemRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.SetAgentKnowledgeBaseAccessForKnowledgeBase(
		r.Context(),
		r.WorkspaceID(),
		r.Subject(),
		r.Request.PathValue("agent_id"),
		r.Request.PathValue("kb_id"),
		r.Body.AccessLevel,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToAgentAccessibleKnowledgeBaseGrant(result)), nil
}

func (h *KBHandler) Create(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseCreateRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	base, err := h.service.CreateKnowledgeBase(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToKnowledgeBase(base, nil)), nil
}

// CreateSetup handles POST /api/v1/kb/setup-sessions.
// It atomically creates an owner-private Draft KB and its recoverable setup session.
func (h *KBHandler) CreateSetup(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseSetupCreateRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	base, setup, err := h.service.CreateKnowledgeBaseSetup(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(dto.KnowledgeBaseSetupCreateResponse{
		KnowledgeBase: converter.ToKnowledgeBase(base, nil),
		Setup:         converter.ToKnowledgeBaseSetup(setup),
	}), nil
}

// StartOfficialEdit creates or resumes one revision-fenced working copy. It
// never changes the current reader-visible Official metadata.
func (h *KBHandler) StartOfficialEdit(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.StartKnowledgeBaseOfficialEdit(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

// ConvertOfficialToDraft atomically unpublishes an Official knowledge base and
// returns the fresh Draft setup session that the administrator can edit.
func (h *KBHandler) ConvertOfficialToDraft(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.ConvertKnowledgeBaseOfficialToDraft(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

func (h *KBHandler) UpdateOfficialEditBasic(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseOfficialEditBasicRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.UpdateKnowledgeBaseOfficialEditBasic(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

func (h *KBHandler) SaveOfficialEdit(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseOfficialEditSaveRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	base, err := h.service.SaveKnowledgeBaseOfficialEdit(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBase(base, nil)), nil
}

// GetSetup handles GET /api/v1/kb/{kb_id}/setup-session.
// It derives step readiness from the current bounded KB source state.
func (h *KBHandler) GetSetup(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.GetKnowledgeBaseSetup(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

// UpdateSetupStep handles PATCH /api/v1/kb/{kb_id}/setup-session.
// It stores the resume cursor behind an exact session-version fence.
func (h *KBHandler) UpdateSetupStep(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseSetupStepRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.UpdateKnowledgeBaseSetupStep(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Body.ActiveStep, r.Body.ExpectedVersion)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

// SetSetupSourceArtifact handles PUT /api/v1/kb/{kb_id}/setup-session/sources/{source_id}/{artifact_kind}.
// It persists only an explicit skip/reset disposition for one optional artifact.
func (h *KBHandler) SetSetupSourceArtifact(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseSetupSourceArtifactRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.SetKnowledgeBaseSetupSourceArtifact(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id"), r.Request.PathValue("artifact_kind"), r.Body.State)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

// RequestDirectWikiGeneration handles the privileged Admin path. Member
// requests use the separate AgentTeam Approval subject adapter and cannot call this
// route as a shortcut.
func (h *KBHandler) RequestDirectWikiGeneration(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseWikiGenerationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.RequestDirectWikiGeneration(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"),
		r.Request.PathValue("source_id"), r.Body.Input(),
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Response{Status: http.StatusAccepted, Body: converter.ToKnowledgeBaseSetup(setup)}, nil
}

func (h *KBHandler) RequestWikiGenerationApproval(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseWikiGenerationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.RequestWikiGenerationApproval(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"),
		r.Request.PathValue("source_id"), r.Body.Input(),
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Response{Status: http.StatusAccepted, Body: converter.ToKnowledgeBaseSetup(setup)}, nil
}

func (h *KBHandler) DecideWikiGenerationApproval(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseWikiGenerationDecisionRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.DecideWikiGenerationApproval(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"),
		r.Request.PathValue("source_id"), r.Request.PathValue("approval_id"),
		r.Body.ExpectedVersion, r.Body.ClientDecisionID, r.Body.Decision, r.Body.Reason,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

func (h *KBHandler) WithdrawWikiGenerationApproval(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseWikiGenerationWithdrawRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.WithdrawWikiGenerationApproval(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"),
		r.Request.PathValue("source_id"), r.Request.PathValue("approval_id"),
		r.Body.ExpectedVersion, r.Body.ClientRequestID,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

func (h *KBHandler) RequestPublicationApproval(r meshxapi.AuthenticatedJSON[dto.KnowledgeBasePublicationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.RequestPublicationApproval(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"),
		r.Body.WorkingRevision, r.Body.IdempotencyKey,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Response{Status: http.StatusAccepted, Body: converter.ToKnowledgeBaseSetup(setup)}, nil
}

func (h *KBHandler) DecidePublicationApproval(r meshxapi.AuthenticatedJSON[dto.KnowledgeBasePublicationDecisionRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.DecidePublicationApproval(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"),
		r.Request.PathValue("approval_id"), r.Body.ExpectedVersion,
		r.Body.ClientDecisionID, r.Body.Decision, r.Body.Reason,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

func (h *KBHandler) WithdrawPublicationApproval(r meshxapi.AuthenticatedJSON[dto.KnowledgeBasePublicationWithdrawRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	setup, err := h.service.WithdrawPublicationApproval(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"),
		r.Request.PathValue("approval_id"), r.Body.ExpectedVersion, r.Body.ClientRequestID,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseSetup(setup)), nil
}

func (h *KBHandler) GetPublicationReview(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	review, err := h.service.GetPublicationReview(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("approval_id"),
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBasePublicationReview(review)), nil
}

func (h *KBHandler) DecidePublicationReview(r meshxapi.AuthenticatedJSON[dto.KnowledgeBasePublicationDecisionRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	review, err := h.service.DecidePublicationReview(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("approval_id"),
		r.Body.ExpectedVersion, r.Body.ClientDecisionID, r.Body.Decision, r.Body.Reason,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBasePublicationReview(review)), nil
}

func (h *KBHandler) Get(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	base, err := h.service.GetKnowledgeBase(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	sources, err := h.service.ListSources(r.Context(), r.WorkspaceID(), r.Subject(), base.ID, 1, maxPerPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBase(base, sources)), nil
}

func (h *KBHandler) Update(r meshxapi.AuthenticatedJSON[dto.KnowledgeBaseUpdateRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	base, err := h.service.UpdateKnowledgeBase(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBase(base, nil)), nil
}

func (h *KBHandler) Archive(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	_, err := h.service.ArchiveKnowledgeBase(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *KBHandler) Pin(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	pin, err := h.service.PinKnowledgeBase(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBasePin(pin)), nil
}

func (h *KBHandler) Unpin(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if err := h.service.UnpinKnowledgeBase(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id")); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *KBHandler) ListPermissions(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	grants, err := h.service.ListKnowledgeBasePermissions(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBPermissionsResponse(grants)), nil
}

func (h *KBHandler) UpsertPermission(r meshxapi.AuthenticatedJSON[dto.KBPermissionGrantRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	input := r.Body.Input()
	input.GrantID = r.Request.PathValue("grant_id")
	grant, err := h.service.UpsertKnowledgeBasePermission(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), input)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBPermissionGrant(grant)), nil
}

// UpdatePermissionRepositoryOverrides handles PATCH /api/v1/kb/{kb_id}/permissions/grants/{grant_id}/repository-overrides.
func (h *KBHandler) UpdatePermissionRepositoryOverrides(r meshxapi.AuthenticatedJSON[dto.KBPermissionRepositoryOverridesRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	grant, err := h.service.UpdateKnowledgeBasePermissionRepositoryOverrides(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("grant_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBPermissionGrant(grant)), nil
}

// ReconcilePermissionRepositoryAccess handles POST /api/v1/kb/{kb_id}/permissions/grants/{grant_id}/repository-access/reconcile.
// It retries repository collaborator materialization for one KB permission grant.
func (h *KBHandler) ReconcilePermissionRepositoryAccess(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	grant, err := h.service.ReconcileKnowledgeBasePermissionRepositoryAccess(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("grant_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBPermissionGrant(grant)), nil
}

func (h *KBHandler) DeletePermission(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if err := h.service.DeleteKnowledgeBasePermission(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("grant_id")); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *KBHandler) Overview(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	overview, err := h.service.GetKnowledgeBaseOverview(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBOverview(overview)), nil
}

func (h *KBHandler) ListSources(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	sources, err := h.service.ListSources(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBSources(sources)), nil
}

func (h *KBHandler) ListRetrievableSources(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	sources, err := h.service.ListRetrievableSources(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBSources(sources)), nil
}

func (h *KBHandler) SourceDetail(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	detail, err := h.service.GetKnowledgeBaseSourceDetail(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBSourceDetail(detail)), nil
}

const (
	agentteamAgentThreadAttestationHeader = "X-AgentTeam-Agent-Thread-Attestation"
	agentteamActingUserVerifiedJWTHeader  = "X-AgentTeam-Acting-User-Verified-JWT"
)

type agentteamAgentThreadAttestation struct {
	WorkspaceID     string
	ThreadChannelID string
	AgentID         string
	ActingUserID    string
}

type agentteamAgentThreadAttestationPayload struct {
	WorkspaceID       string `json:"workspaceId"`
	WorkspaceIDSnake  string `json:"workspace_id"`
	ThreadChannelID   string `json:"threadChannelId"`
	ThreadIDSnake     string `json:"thread_channel_id"`
	AgentID           string `json:"agentId"`
	AgentIDSnake      string `json:"agent_id"`
	ActingUserID      string `json:"actingUserId"`
	ActingUserIDSnake string `json:"acting_user_id"`
}

func decodeAgentTeamAgentThreadAttestation(r *http.Request) (agentteamAgentThreadAttestation, error) {
	encoded := ""
	if r != nil {
		encoded = strings.TrimSpace(r.Header.Get(agentteamAgentThreadAttestationHeader))
	}
	if encoded == "" {
		return agentteamAgentThreadAttestation{}, meshxapi.NewHTTPError(http.StatusBadRequest, "Missing AgentTeam agent thread attestation")
	}
	payload, err := base64.RawURLEncoding.DecodeString(encoded)
	if err != nil {
		return agentteamAgentThreadAttestation{}, meshxapi.NewHTTPError(http.StatusBadRequest, "Invalid AgentTeam agent thread attestation")
	}
	var wire agentteamAgentThreadAttestationPayload
	if err := json.Unmarshal(payload, &wire); err != nil {
		return agentteamAgentThreadAttestation{}, meshxapi.NewHTTPError(http.StatusBadRequest, "Invalid AgentTeam agent thread attestation")
	}
	attestation := agentteamAgentThreadAttestation{
		WorkspaceID:     firstNonEmptyTrimmed(wire.WorkspaceID, wire.WorkspaceIDSnake),
		ThreadChannelID: firstNonEmptyTrimmed(wire.ThreadChannelID, wire.ThreadIDSnake),
		AgentID:         firstNonEmptyTrimmed(wire.AgentID, wire.AgentIDSnake),
		ActingUserID:    firstNonEmptyTrimmed(wire.ActingUserID, wire.ActingUserIDSnake),
	}
	if strings.TrimSpace(attestation.AgentID) == "" || strings.TrimSpace(attestation.ThreadChannelID) == "" {
		return agentteamAgentThreadAttestation{}, meshxapi.NewHTTPError(http.StatusBadRequest, "Incomplete AgentTeam agent thread attestation")
	}
	return attestation, nil
}

func kbKnowledgeBody(r *http.Request, attestation agentteamAgentThreadAttestation) (map[string]json.RawMessage, error) {
	fields := map[string]json.RawMessage{}
	if r != nil && r.Body != nil {
		defer r.Body.Close()
		payload, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
		if err != nil {
			return nil, meshxapi.NewHTTPError(http.StatusBadRequest, "Invalid JSON body")
		}
		if len(strings.TrimSpace(string(payload))) > 0 {
			if err := json.Unmarshal(payload, &fields); err != nil {
				return nil, meshxapi.NewHTTPError(http.StatusBadRequest, "Invalid JSON body")
			}
		}
	}
	normalizeKBKnowledgeBodyFields(fields)
	agentID, _ := json.Marshal(strings.TrimSpace(attestation.AgentID))
	fields["agentId"] = agentID
	delete(fields, "threadChannelId")
	delete(fields, "thread_channel_id")
	return fields, nil
}

func normalizeKBKnowledgeBodyFields(fields map[string]json.RawMessage) {
	copyPreferredJSONField(fields, "kbIds", "kb_ids")
	copyPreferredJSONField(fields, "sourceIds", "source_ids")
	copyPreferredJSONField(fields, "threadChannelId", "thread_channel_id")
	copyPreferredJSONField(fields, "citationUri", "citation_uri")
	copyPreferredJSONField(fields, "sourceId", "source_id")
	copyPreferredJSONField(fields, "repoId", "repo_id")
	copyPreferredJSONField(fields, "repoRole", "repo_role")
	copyPreferredJSONField(fields, "wikiSlug", "wiki_slug")
	copyPreferredJSONField(fields, "searchMode", "search_mode")
	copyPreferredJSONField(fields, "maxResults", "max_results")
	copyPreferredJSONField(fields, "citationIds", "citation_ids")
	copyPreferredJSONField(fields, "maxCharsPerItem", "max_chars_per_item")
	copyPreferredJSONField(fields, "titleOnly", "title_only")
	copyPreferredJSONField(fields, "slugPrefix", "slug_prefix")
	copyPreferredJSONField(fields, "headingsOnly", "headings_only")
	copyPreferredJSONField(fields, "maxBytes", "max_bytes")
	copyPreferredJSONField(fields, "includePages", "include_pages")
	copyPreferredJSONField(fields, "includeDirs", "include_dirs")
}

func copyPreferredJSONField(fields map[string]json.RawMessage, canonical, alias string) {
	if _, ok := fields[canonical]; !ok {
		if value, hasAlias := fields[alias]; hasAlias {
			fields[canonical] = value
		}
	}
	delete(fields, alias)
}

func firstNonEmptyTrimmed(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func bindKBKnowledge[T any](fields map[string]json.RawMessage) (T, error) {
	var request T
	payload, err := json.Marshal(fields)
	if err != nil {
		return request, err
	}
	if err := json.Unmarshal(payload, &request); err != nil {
		return request, meshxapi.NewHTTPError(http.StatusBadRequest, "Invalid JSON body")
	}
	return request, nil
}

func agentteamActingUserAuthorizationHeader(r *http.Request, attestation agentteamAgentThreadAttestation) (string, error) {
	if r == nil {
		return "", nil
	}
	raw := strings.TrimSpace(r.Header.Get(agentteamActingUserVerifiedJWTHeader))
	if raw == "" {
		return "", nil
	}
	if strings.TrimSpace(attestation.ActingUserID) == "" {
		return "", meshxapi.NewHTTPError(http.StatusForbidden, "AgentTeam acting user auth requires acting user attestation")
	}
	identity, token, err := trusted.IdentityFromRequest(r, trusted.Options{Header: agentteamActingUserVerifiedJWTHeader})
	if err != nil {
		return "", meshxapi.NewHTTPError(http.StatusForbidden, "Invalid AgentTeam acting user auth")
	}
	if strings.TrimSpace(identity.Subject) != strings.TrimSpace(attestation.ActingUserID) {
		return "", meshxapi.NewHTTPError(http.StatusForbidden, "AgentTeam acting user auth subject mismatch")
	}
	if hasBearerPrefix(raw) {
		return raw, nil
	}
	if strings.TrimSpace(token) == "" {
		return "", meshxapi.NewHTTPError(http.StatusForbidden, "Invalid AgentTeam acting user auth")
	}
	return "Bearer " + strings.TrimSpace(token), nil
}

func hasBearerPrefix(raw string) bool {
	fields := strings.Fields(strings.TrimSpace(raw))
	return len(fields) == 2 && strings.EqualFold(fields[0], "Bearer")
}

func (h *KBHandler) KBKnowledge(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	attestation, err := decodeAgentTeamAgentThreadAttestation(r.Request)
	if err != nil {
		return meshxapi.Response{}, err
	}
	if workspaceID := strings.TrimSpace(attestation.WorkspaceID); workspaceID != "" && workspaceID != r.WorkspaceID() {
		return meshxapi.Response{}, meshxapi.NewHTTPError(http.StatusForbidden, "AgentTeam knowledge attestation workspace mismatch")
	}
	actingUserAuth, err := agentteamActingUserAuthorizationHeader(r.Request, attestation)
	if err != nil {
		return meshxapi.Response{}, err
	}
	fields, err := kbKnowledgeBody(r.Request, attestation)
	if err != nil {
		return meshxapi.Response{}, err
	}
	switch strings.TrimSpace(r.Request.PathValue("operation")) {
	case "context":
		body, err := bindKBKnowledge[dto.KBRetrievalContextRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		result, err := h.service.DescribeKnowledgeContext(r.Context(), r.WorkspaceID(), r.Subject(), body.Input(attestation.ActingUserID))
		if err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBRetrievalContextResponse(result)), nil
	case "capabilities":
		body, err := bindKBKnowledge[dto.KBCapabilitiesRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		result, err := h.service.DescribeKnowledgeCapabilities(r.Context(), r.WorkspaceID(), r.Subject(), body.Input(attestation.ActingUserID))
		if err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBCapabilitiesResponse(result)), nil
	case "search":
		body, err := bindKBKnowledge[dto.KBRetrievalSearchRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		input := body.Input(r.Request.Header.Get("Authorization"), attestation.ActingUserID)
		input.ActingUserAuthorizationHeader = actingUserAuth
		result, err := h.service.SearchKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), input)
		if err != nil {
			if structured, ok := kbKnowledgeStructuredPayloadError(err); ok {
				return meshxapi.OK(converter.ToKBRetrievalSearchResponse(kbservice.KBSearchResult{Error: structured})), nil
			}
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBRetrievalSearchResponse(result)), nil
	case "fetch":
		body, err := bindKBKnowledge[dto.KBRetrievalFetchRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		input := body.Input(r.Request.Header.Get("Authorization"), attestation.ActingUserID)
		input.ActingUserAuthorizationHeader = actingUserAuth
		result, err := h.service.FetchKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), input)
		if err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBRetrievalFetchResponse(result)), nil
	case "read":
		body, err := bindKBKnowledge[dto.KBRetrievalReadRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		input := body.Input(r.Request.Header.Get("Authorization"), attestation.ActingUserID)
		input.ActingUserAuthorizationHeader = actingUserAuth
		page, err := h.service.ReadKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), input)
		if err != nil {
			if structured, ok := kbKnowledgeStructuredPayloadError(err); ok {
				return meshxapi.OK(converter.ToKBRetrievalReadResponse(kbservice.KBReadResult{Error: structured})), nil
			}
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBRetrievalReadResponse(page)), nil
	case "tree":
		body, err := bindKBKnowledge[dto.KBRetrievalTreeRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		input := body.Input(r.Request.Header.Get("Authorization"), attestation.ActingUserID)
		input.ActingUserAuthorizationHeader = actingUserAuth
		tree, err := h.service.ListKnowledgeTree(r.Context(), r.WorkspaceID(), r.Subject(), input)
		if err != nil {
			if structured, ok := kbKnowledgeStructuredPayloadError(err); ok {
				return meshxapi.OK(converter.ToKBRetrievalTreeResponse(kbservice.KBTreeResult{Error: structured})), nil
			}
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBRetrievalTreeResponse(tree)), nil
	case "manifest", "summary":
		body, err := bindKBKnowledge[dto.KBManifestRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		input := body.Input(r.Request.Header.Get("Authorization"), attestation.ActingUserID)
		input.ActingUserAuthorizationHeader = actingUserAuth
		result, err := h.service.BuildKnowledgeManifest(r.Context(), r.WorkspaceID(), r.Subject(), input)
		if err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBManifestResponse(result)), nil
	case "stat":
		body, err := bindKBKnowledge[dto.KBStatRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		input := body.Input(r.Request.Header.Get("Authorization"), attestation.ActingUserID)
		input.ActingUserAuthorizationHeader = actingUserAuth
		result, err := h.service.StatKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), input)
		if err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBStatResponse(result)), nil
	case "batch_read":
		body, err := bindKBKnowledge[dto.KBBatchReadRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		input := body.Input(r.Request.Header.Get("Authorization"), attestation.ActingUserID)
		input.ActingUserAuthorizationHeader = actingUserAuth
		result, err := h.service.BatchReadKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), input)
		if err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBBatchReadResponse(result)), nil
	case "batch_tree":
		body, err := bindKBKnowledge[dto.KBBatchTreeRequest](fields)
		if err != nil {
			return meshxapi.Response{}, err
		}
		input := body.Input(r.Request.Header.Get("Authorization"), attestation.ActingUserID)
		input.ActingUserAuthorizationHeader = actingUserAuth
		result, err := h.service.BatchKnowledgeTree(r.Context(), r.WorkspaceID(), r.Subject(), input)
		if err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(converter.ToKBBatchTreeResponse(result)), nil
	default:
		return meshxapi.Response{}, meshxapi.NewHTTPError(http.StatusNotFound, "Unknown KB knowledge operation")
	}
}

func kbKnowledgeStructuredPayloadError(err error) (*kbservice.KBStructuredError, bool) {
	if err == nil || errors.Is(err, kbservice.ErrUnauthorized) || errors.Is(err, kbservice.ErrForbidden) {
		return nil, false
	}
	structured := kbservice.StructuredKBError(err)
	switch structured.Code {
	case "not_found", "index_not_ready", "upstream_error":
		return structured, true
	default:
		return nil, false
	}
}

func (h *KBHandler) RetrievalContext(r meshxapi.AuthenticatedJSON[dto.KBRetrievalContextRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.DescribeKnowledgeContext(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBRetrievalContextResponse(result)), nil
}

func (h *KBHandler) RetrievalSearch(r meshxapi.AuthenticatedJSON[dto.KBRetrievalSearchRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.SearchKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.Header.Get("Authorization")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBRetrievalSearchResponse(result)), nil
}

func (h *KBHandler) RetrievalFetch(r meshxapi.AuthenticatedJSON[dto.KBRetrievalFetchRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.FetchKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.Header.Get("Authorization")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBRetrievalFetchResponse(result)), nil
}

func (h *KBHandler) RetrievalRead(r meshxapi.AuthenticatedJSON[dto.KBRetrievalReadRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, err := h.service.ReadKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.Header.Get("Authorization")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBRetrievalReadResponse(page)), nil
}

func (h *KBHandler) RetrievalTree(r meshxapi.AuthenticatedJSON[dto.KBRetrievalTreeRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	tree, err := h.service.ListKnowledgeTree(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.Header.Get("Authorization")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBRetrievalTreeResponse(tree)), nil
}

func (h *KBHandler) RetrievalCapabilities(r meshxapi.AuthenticatedJSON[dto.KBCapabilitiesRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.DescribeKnowledgeCapabilities(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBCapabilitiesResponse(result)), nil
}

func (h *KBHandler) RetrievalManifest(r meshxapi.AuthenticatedJSON[dto.KBManifestRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.BuildKnowledgeManifest(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.Header.Get("Authorization")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBManifestResponse(result)), nil
}

func (h *KBHandler) RetrievalStat(r meshxapi.AuthenticatedJSON[dto.KBStatRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.StatKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.Header.Get("Authorization")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBStatResponse(result)), nil
}

func (h *KBHandler) RetrievalBatchRead(r meshxapi.AuthenticatedJSON[dto.KBBatchReadRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.BatchReadKnowledge(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.Header.Get("Authorization")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBBatchReadResponse(result)), nil
}

func (h *KBHandler) RetrievalBatchTree(r meshxapi.AuthenticatedJSON[dto.KBBatchTreeRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.service.BatchKnowledgeTree(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input(r.Request.Header.Get("Authorization")))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBBatchTreeResponse(result)), nil
}

func (h *KBHandler) CreateSource(r meshxapi.AuthenticatedJSON[dto.KBSourceMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	input := r.Body.Input()
	input.SourceRepositoryActor = repos.ResourceRepositoryActorFromAgentTeam(r.Identity)
	source, err := h.service.CreateSource(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), input)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToKBSource(source)), nil
}

func (h *KBHandler) UpdateSource(r meshxapi.AuthenticatedJSON[dto.KBSourceMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	source, err := h.service.UpdateSource(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBSource(source)), nil
}

func (h *KBHandler) DeleteSource(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if err := h.service.DeleteSource(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id")); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *KBHandler) UpdateSourceMirror(r meshxapi.AuthenticatedJSON[dto.KBSourceMirrorMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	mirror, err := h.service.UpdateSourceMirror(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBSourceMirrorSummary(mirror)), nil
}

func (h *KBHandler) RequestSourceMirrorSync(r meshxapi.AuthenticatedJSON[dto.KBSourceMirrorSyncRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	job, err := h.service.RequestSourceMirrorSync(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id"), r.Body.Ref)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Response{Status: http.StatusAccepted, Body: converter.ToKBSourceMirrorSyncJob(job)}, nil
}

func (h *KBHandler) ListSourceMirrorSyncs(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	result, err := h.service.ListSourceMirrorSyncs(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBSourceMirrorSyncListResponse(result)), nil
}

func (h *KBHandler) ListAuditEvents(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	events, err := h.service.ListAuditEvents(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBAuditEvents(events)), nil
}

func (h *KBHandler) ListSavedViews(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	views, err := h.service.ListKBSavedViews(r.Context(), r.WorkspaceID(), r.Subject(), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBSavedViews(views)), nil
}

func (h *KBHandler) CreateSavedView(r meshxapi.AuthenticatedJSON[dto.KBSavedViewMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	view, err := h.service.CreateKBSavedView(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToKBSavedView(view)), nil
}

func (h *KBHandler) UpdateSavedView(r meshxapi.AuthenticatedJSON[dto.KBSavedViewMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	view, err := h.service.UpdateKBSavedView(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("view_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKBSavedView(view)), nil
}

func (h *KBHandler) DeleteSavedView(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	err := h.service.DeleteKBSavedView(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("view_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *KBHandler) ListSavedViewKnowledgeBases(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	result, err := h.service.ListKBSavedViewKnowledgeBases(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("view_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToKnowledgeBaseListResponse(result.Items, result.GroupCounts, result.Page, result.PerPage, result.Total, result.HasMore)), nil
}

func pagination(r *http.Request) (int, int) {
	page := parsePositiveInt(r.URL.Query().Get("page"), 1)
	perPage := parsePositiveInt(r.URL.Query().Get("per_page"), 50)
	if perPage > maxPerPage {
		perPage = maxPerPage
	}
	return page, perPage
}

func activityListOptions(r *http.Request) (activity.ResourceActivityListOptions, error) {
	if r == nil || r.URL == nil {
		return activity.ResourceActivityListOptions{Limit: 50}, nil
	}
	query := r.URL.Query()
	limit := parsePositiveInt(query.Get("limit"), 0)
	if limit <= 0 {
		limit = parsePositiveInt(query.Get("per_page"), 50)
	}
	since, err := optionalQueryTime(query.Get("since"), "since")
	if err != nil {
		return activity.ResourceActivityListOptions{}, err
	}
	until, err := optionalQueryTime(query.Get("until"), "until")
	if err != nil {
		return activity.ResourceActivityListOptions{}, err
	}
	return activity.ResourceActivityListOptions{
		AssetID:   query.Get("assetId"),
		EventType: query.Get("eventType"),
		ActorID:   query.Get("actorId"),
		Cursor:    query.Get("cursor"),
		Limit:     limit,
		Since:     since,
		Until:     until,
	}, nil
}

func optionalQueryTime(raw, field string) (*time.Time, error) {
	if strings.TrimSpace(raw) == "" {
		return nil, nil
	}
	parsed, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(raw))
	if err != nil {
		return nil, meshxapi.NewHTTPError(http.StatusBadRequest, "invalid "+field)
	}
	parsed = parsed.UTC()
	return &parsed, nil
}

func parsePositiveInt(raw string, fallback int) int {
	value, err := strconv.Atoi(raw)
	if err != nil || value <= 0 {
		return fallback
	}
	return value
}

func kbErrorHandling() meshxapi.HandlerOption {
	return meshxapi.WithErrorClassifier(func(err error) (meshxapi.ErrorResponse, bool) {
		if errors.Is(err, activity.ErrInvalidArgument) || errors.Is(err, activity.ErrInvalidCursor) {
			return meshxapi.ErrorResponse{Status: http.StatusBadRequest, Message: err.Error()}, true
		}
		if errors.Is(err, kbservice.ErrUnauthorized) || errors.Is(err, kbservice.ErrInvalidArgument) || errors.Is(err, kbservice.ErrForbidden) || errors.Is(err, kbservice.ErrNotFound) || errors.Is(err, kbservice.ErrConflict) {
			return meshxapi.ErrorResponse{Status: kbservice.HTTPStatus(err), Message: err.Error(), Code: kbservice.Code(err), Details: kbservice.Details(err)}, true
		}
		if errors.Is(err, repos.ErrUnauthorized) || errors.Is(err, repos.ErrInvalidArgument) || errors.Is(err, repos.ErrForbidden) || errors.Is(err, repos.ErrNotFound) || errors.Is(err, repos.ErrConflict) {
			return meshxapi.ErrorResponse{Status: repos.HTTPStatus(err), Message: err.Error(), Code: repos.Code(err), Details: repos.Details(err)}, true
		}
		return meshxapi.ErrorResponse{}, false
	})
}
