package message

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
)

const ReviewFeedbackMetadataKey = "reviewFeedback"

const (
	ReviewFeedbackStatusOpen     = "open"
	ReviewFeedbackStatusResolved = "resolved"
)

const ReviewFeedbackDecisionApproved = "approved"

const (
	reviewFeedbackVersion             = 1
	maxReviewFeedbackItems            = 100
	maxReviewFeedbackSummaryRunes     = 4000
	maxReviewFeedbackBodyRunes        = 8000
	maxReviewFeedbackCodePreviewRunes = 240
)

const (
	maxReviewFeedbackMetadataBodyRunes        = 4000
	maxReviewFeedbackMetadataHunkHeaderRunes  = 400
	maxReviewFeedbackMetadataPathRunes        = 512
	maxReviewFeedbackMetadataRefRunes         = 160
	maxReviewFeedbackMetadataDisplayNameRunes = 160
	maxReviewFeedbackMetadataFingerprintRunes = 160
	maxReviewFeedbackMetadataCodePreviewRunes = 240
)

const (
	maxReviewFeedbackMessageQuotePreviewRunes = 600
	maxReviewFeedbackMessageQuotePreviewLines = 8
	maxReviewFeedbackCodePreviewLines         = 8
)

type ReviewFeedbackContextSnapshot struct {
	ReviewContextID string `json:"reviewContextId"`
	SubjectType     string `json:"subjectType,omitempty"`
	SubjectID       string `json:"subjectId,omitempty"`
	SourceAgentID   string `json:"sourceAgentId,omitempty"`
	Kind            string `json:"kind,omitempty"`
	DisplayName     string `json:"displayName,omitempty"`
	RootPath        string `json:"rootPath,omitempty"`
	HeadRef         string `json:"headRef,omitempty"`
	BaseRef         string `json:"baseRef,omitempty"`
	ComparisonMode  string `json:"comparisonMode,omitempty"`
	PRURL           string `json:"prUrl,omitempty"`
}

type ReviewFeedbackItemInput struct {
	ID                string         `json:"id,omitempty"`
	Type              string         `json:"type,omitempty"`
	Kind              string         `json:"kind,omitempty"`
	Path              string         `json:"path,omitempty"`
	OldPath           string         `json:"oldPath,omitempty"`
	Side              string         `json:"side,omitempty"`
	OldLine           *int64         `json:"oldLine,omitempty"`
	NewLine           *int64         `json:"newLine,omitempty"`
	StartLine         *int64         `json:"startLine,omitempty"`
	EndLine           *int64         `json:"endLine,omitempty"`
	HunkHeader        string         `json:"hunkHeader,omitempty"`
	AnchorFingerprint string         `json:"anchorFingerprint,omitempty"`
	CodePreview       string         `json:"codePreview,omitempty"`
	Body              string         `json:"body"`
	Anchor            map[string]any `json:"anchor,omitempty"`
	Metadata          map[string]any `json:"metadata,omitempty"`
}

type ReviewFeedbackBuildRequest struct {
	Context             ReviewFeedbackContextSnapshot `json:"context"`
	Locale              string                        `json:"locale,omitempty"`
	Decision            string                        `json:"decision,omitempty"`
	Summary             string                        `json:"summary,omitempty"`
	Refs                map[string]any                `json:"refs,omitempty"`
	Items               []ReviewFeedbackItemInput     `json:"items"`
	SubmittedByMemberID string                        `json:"submittedByMemberId"`
	SubmittedAt         time.Time                     `json:"submittedAt"`
	ClientMessageID     string                        `json:"clientMessageId,omitempty"`
}

type ReviewFeedbackPayload struct {
	Version             int                           `json:"version"`
	Kind                string                        `json:"kind"`
	ReviewContextID     string                        `json:"reviewContextId"`
	SubjectType         string                        `json:"subjectType,omitempty"`
	SubjectID           string                        `json:"subjectId,omitempty"`
	Context             ReviewFeedbackContextSnapshot `json:"context"`
	Locale              string                        `json:"locale,omitempty"`
	Decision            string                        `json:"decision,omitempty"`
	Summary             string                        `json:"summary,omitempty"`
	Refs                map[string]any                `json:"refs,omitempty"`
	Items               []ReviewFeedbackItem          `json:"items"`
	SubmittedByMemberID string                        `json:"submittedByMemberId,omitempty"`
	SubmittedAt         string                        `json:"submittedAt,omitempty"`
}

type ReviewFeedbackItem struct {
	ID                 string         `json:"id"`
	Type               string         `json:"type,omitempty"`
	Kind               string         `json:"kind,omitempty"`
	Path               string         `json:"path,omitempty"`
	OldPath            string         `json:"oldPath,omitempty"`
	Side               string         `json:"side,omitempty"`
	OldLine            *int64         `json:"oldLine,omitempty"`
	NewLine            *int64         `json:"newLine,omitempty"`
	StartLine          *int64         `json:"startLine,omitempty"`
	EndLine            *int64         `json:"endLine,omitempty"`
	HunkHeader         string         `json:"hunkHeader,omitempty"`
	AnchorFingerprint  string         `json:"anchorFingerprint,omitempty"`
	CodePreview        string         `json:"codePreview,omitempty"`
	Body               string         `json:"body"`
	Anchor             map[string]any `json:"anchor,omitempty"`
	Metadata           map[string]any `json:"metadata,omitempty"`
	Status             string         `json:"status"`
	CreatedByMemberID  string         `json:"createdByMemberId,omitempty"`
	CreatedAt          string         `json:"createdAt,omitempty"`
	ResolvedByMemberID string         `json:"resolvedByMemberId,omitempty"`
	ResolvedAt         string         `json:"resolvedAt,omitempty"`
}

func BuildReviewFeedbackPayload(request ReviewFeedbackBuildRequest) (ReviewFeedbackPayload, error) {
	contextSnapshot := normalizeReviewFeedbackContextSnapshot(request.Context)
	if contextSnapshot.ReviewContextID == "" {
		return ReviewFeedbackPayload{}, badRequest("reviewContextId is required")
	}
	decision := NormalizeReviewFeedbackDecision(request.Decision)
	if strings.TrimSpace(request.Decision) != "" && decision == "" {
		return ReviewFeedbackPayload{}, badRequest("review feedback decision must be approved")
	}
	if len(request.Items) == 0 && decision == "" {
		return ReviewFeedbackPayload{}, badRequest("at least one review feedback item or decision is required")
	}
	if len(request.Items) > maxReviewFeedbackItems {
		return ReviewFeedbackPayload{}, badRequest(fmt.Sprintf("too many review feedback items (max %d)", maxReviewFeedbackItems))
	}

	submittedAt := request.SubmittedAt.UTC()
	createdAt := ""
	if !submittedAt.IsZero() {
		createdAt = submittedAt.Format(time.RFC3339Nano)
	}
	clientMessageID := strings.TrimSpace(request.ClientMessageID)
	items := make([]ReviewFeedbackItem, 0, len(request.Items))
	seenIDs := make(map[string]struct{}, len(request.Items))
	for index, input := range request.Items {
		item, err := normalizeReviewFeedbackItemInput(input, index, clientMessageID, strings.TrimSpace(request.SubmittedByMemberID), createdAt)
		if err != nil {
			return ReviewFeedbackPayload{}, err
		}
		if _, ok := seenIDs[item.ID]; ok {
			return ReviewFeedbackPayload{}, badRequest("review feedback item ids must be unique")
		}
		seenIDs[item.ID] = struct{}{}
		items = append(items, item)
	}

	summary := trimRunes(strings.TrimSpace(request.Summary), maxReviewFeedbackSummaryRunes)
	payload := ReviewFeedbackPayload{
		Version:             reviewFeedbackVersion,
		Kind:                "review_feedback",
		ReviewContextID:     contextSnapshot.ReviewContextID,
		SubjectType:         contextSnapshot.SubjectType,
		SubjectID:           contextSnapshot.SubjectID,
		Context:             contextSnapshot,
		Locale:              normalizeReviewFeedbackLocale(request.Locale),
		Decision:            decision,
		Summary:             summary,
		Refs:                copyMap(request.Refs),
		Items:               items,
		SubmittedByMemberID: strings.TrimSpace(request.SubmittedByMemberID),
		SubmittedAt:         createdAt,
	}
	return payload, nil
}

func normalizeReviewFeedbackContextSnapshot(snapshot ReviewFeedbackContextSnapshot) ReviewFeedbackContextSnapshot {
	return ReviewFeedbackContextSnapshot{
		ReviewContextID: strings.TrimSpace(snapshot.ReviewContextID),
		SubjectType:     strings.TrimSpace(snapshot.SubjectType),
		SubjectID:       strings.TrimSpace(snapshot.SubjectID),
		SourceAgentID:   strings.TrimSpace(snapshot.SourceAgentID),
		Kind:            strings.TrimSpace(snapshot.Kind),
		DisplayName:     strings.TrimSpace(snapshot.DisplayName),
		RootPath:        strings.TrimSpace(snapshot.RootPath),
		HeadRef:         strings.TrimSpace(snapshot.HeadRef),
		BaseRef:         strings.TrimSpace(snapshot.BaseRef),
		ComparisonMode:  strings.TrimSpace(snapshot.ComparisonMode),
		PRURL:           strings.TrimSpace(snapshot.PRURL),
	}
}

func normalizeReviewFeedbackItemInput(input ReviewFeedbackItemInput, index int, clientMessageID string, submittedByMemberID string, createdAt string) (ReviewFeedbackItem, error) {
	body := trimRunes(strings.TrimSpace(input.Body), maxReviewFeedbackBodyRunes)
	if body == "" {
		return ReviewFeedbackItem{}, badRequest("review feedback item body is required")
	}
	id := strings.TrimSpace(input.ID)
	if id == "" {
		var err error
		id, err = generateReviewFeedbackItemID(clientMessageID, index)
		if err != nil {
			return ReviewFeedbackItem{}, err
		}
	}
	anchor := copyMap(input.Anchor)
	oldLine := copyInt64Ptr(input.OldLine)
	newLine := copyInt64Ptr(input.NewLine)
	startLine := copyInt64Ptr(input.StartLine)
	endLine := copyInt64Ptr(input.EndLine)
	if oldLine == nil {
		oldLine = numericAnchorValuePtr(anchor, "oldLine")
	}
	if newLine == nil {
		newLine = firstNumericAnchorValuePtr(anchor, "newLine", "line")
	}
	if startLine == nil {
		startLine = firstNumericAnchorValuePtr(anchor, "startLine", "lineStart")
	}
	if endLine == nil {
		endLine = firstNumericAnchorValuePtr(anchor, "endLine", "lineEnd")
	}
	startLine, endLine = normalizeReviewFeedbackLineRange(startLine, endLine)
	side := normalizeReviewFeedbackSide(input.Side, oldLine, newLine)
	itemType := strings.TrimSpace(input.Type)
	if itemType == "" {
		itemType = reviewFeedbackTypeFromLegacyKind(input.Kind, input.Path, anchor, oldLine, newLine, startLine, endLine)
	}
	kind := strings.TrimSpace(input.Kind)
	if kind == "" {
		kind = reviewFeedbackLegacyKind(itemType)
	}
	return ReviewFeedbackItem{
		ID:                id,
		Type:              itemType,
		Kind:              kind,
		Path:              strings.TrimSpace(input.Path),
		OldPath:           strings.TrimSpace(input.OldPath),
		Side:              side,
		OldLine:           oldLine,
		NewLine:           newLine,
		StartLine:         startLine,
		EndLine:           endLine,
		HunkHeader:        strings.TrimSpace(input.HunkHeader),
		AnchorFingerprint: strings.TrimSpace(input.AnchorFingerprint),
		CodePreview:       normalizeReviewFeedbackCodePreview(input.CodePreview, maxReviewFeedbackCodePreviewRunes),
		Body:              body,
		Anchor:            anchor,
		Metadata:          copyMap(input.Metadata),
		Status:            ReviewFeedbackStatusOpen,
		CreatedByMemberID: submittedByMemberID,
		CreatedAt:         createdAt,
	}, nil
}

func generateReviewFeedbackItemID(clientMessageID string, index int) (string, error) {
	if clientMessageID != "" {
		sum := sha256.Sum256([]byte(fmt.Sprintf("%s:%d", clientMessageID, index)))
		return "rvfi_" + hex.EncodeToString(sum[:])[:14], nil
	}
	id, err := identifiers.NewEntityID("rvfi")
	if err != nil {
		return "", fmt.Errorf("generate review feedback item id: %w", err)
	}
	return id, nil
}

func BuildReviewFeedbackMetadata(payload ReviewFeedbackPayload) (map[string]any, error) {
	bodyLimits := []int{maxReviewFeedbackMetadataBodyRunes, 1200, 512, 160, 0}
	var lastErr error
	for _, bodyLimit := range bodyLimits {
		metadata, err := buildReviewFeedbackMetadata(compactReviewFeedbackPayloadForMetadata(payload, bodyLimit))
		if err == nil {
			return metadata, nil
		}
		if !reviewFeedbackMetadataTooLarge(err) {
			return nil, err
		}
		lastErr = err
	}
	return nil, lastErr
}

func buildReviewFeedbackMetadata(payload ReviewFeedbackPayload) (map[string]any, error) {
	value, err := reviewFeedbackPayloadToMap(payload)
	if err != nil {
		return nil, err
	}
	metadata := map[string]any{ReviewFeedbackMetadataKey: value}
	if _, _, err := EncodeMessageMetadata(metadata); err != nil {
		return nil, err
	}
	return metadata, nil
}

func ReviewFeedbackPayloadFromMetadata(metadata map[string]any) (ReviewFeedbackPayload, bool, error) {
	if len(metadata) == 0 {
		return ReviewFeedbackPayload{}, false, nil
	}
	raw, ok := metadata[ReviewFeedbackMetadataKey]
	if !ok {
		return ReviewFeedbackPayload{}, false, nil
	}
	encoded, err := json.Marshal(raw)
	if err != nil {
		return ReviewFeedbackPayload{}, true, fmt.Errorf("encode review feedback metadata: %w", err)
	}
	var payload ReviewFeedbackPayload
	if err := json.Unmarshal(encoded, &payload); err != nil {
		return ReviewFeedbackPayload{}, true, fmt.Errorf("decode review feedback metadata: %w", err)
	}
	if payload.ReviewContextID == "" {
		payload.ReviewContextID = strings.TrimSpace(payload.Context.ReviewContextID)
	}
	if payload.SubjectType == "" {
		payload.SubjectType = strings.TrimSpace(payload.Context.SubjectType)
	}
	if payload.SubjectID == "" {
		payload.SubjectID = strings.TrimSpace(payload.Context.SubjectID)
	}
	return payload, true, nil
}

func UpdateReviewFeedbackItemStatus(metadata map[string]any, itemID string, status string, memberID string, at time.Time) (map[string]any, ReviewFeedbackPayload, error) {
	payload, ok, err := ReviewFeedbackPayloadFromMetadata(metadata)
	if err != nil {
		return nil, ReviewFeedbackPayload{}, err
	}
	if !ok {
		return nil, ReviewFeedbackPayload{}, badRequest("message is not review feedback")
	}
	normalizedItemID := strings.TrimSpace(itemID)
	if normalizedItemID == "" {
		return nil, ReviewFeedbackPayload{}, badRequest("itemId is required")
	}
	normalizedStatus := NormalizeReviewFeedbackStatus(status)
	if normalizedStatus == "" {
		return nil, ReviewFeedbackPayload{}, badRequest("status must be open or resolved")
	}
	updatedAt := at.UTC()
	if updatedAt.IsZero() {
		updatedAt = time.Now().UTC()
	}
	found := false
	for index := range payload.Items {
		if strings.TrimSpace(payload.Items[index].ID) != normalizedItemID {
			continue
		}
		found = true
		payload.Items[index].Status = normalizedStatus
		if normalizedStatus == ReviewFeedbackStatusResolved {
			payload.Items[index].ResolvedByMemberID = strings.TrimSpace(memberID)
			payload.Items[index].ResolvedAt = updatedAt.Format(time.RFC3339Nano)
		} else {
			payload.Items[index].ResolvedByMemberID = ""
			payload.Items[index].ResolvedAt = ""
		}
		break
	}
	if !found {
		return nil, ReviewFeedbackPayload{}, notFound("review feedback item not found")
	}
	value, err := reviewFeedbackPayloadToMap(compactReviewFeedbackPayloadForMetadata(payload, maxReviewFeedbackMetadataBodyRunes))
	if err != nil {
		return nil, ReviewFeedbackPayload{}, err
	}
	updatedMetadata := CopyMessageMetadata(metadata)
	if updatedMetadata == nil {
		updatedMetadata = map[string]any{}
	}
	updatedMetadata[ReviewFeedbackMetadataKey] = value
	if _, _, err := EncodeMessageMetadata(updatedMetadata); err != nil {
		if !reviewFeedbackMetadataTooLarge(err) {
			return nil, ReviewFeedbackPayload{}, err
		}
		compacted, compactErr := BuildReviewFeedbackMetadata(payload)
		if compactErr != nil {
			return nil, ReviewFeedbackPayload{}, compactErr
		}
		for key, value := range compacted {
			updatedMetadata[key] = value
		}
		if _, _, err := EncodeMessageMetadata(updatedMetadata); err != nil {
			return nil, ReviewFeedbackPayload{}, err
		}
	}
	return updatedMetadata, payload, nil
}

func compactReviewFeedbackPayloadForMetadata(payload ReviewFeedbackPayload, bodyLimit int) ReviewFeedbackPayload {
	compacted := payload
	compacted.Context = compactReviewFeedbackContextSnapshot(payload.Context)
	compacted.Summary = ""
	compacted.Refs = nil
	compacted.Items = make([]ReviewFeedbackItem, 0, len(payload.Items))
	for _, item := range payload.Items {
		compacted.Items = append(compacted.Items, compactReviewFeedbackItemForMetadata(item, bodyLimit))
	}
	return compacted
}

func compactReviewFeedbackContextSnapshot(snapshot ReviewFeedbackContextSnapshot) ReviewFeedbackContextSnapshot {
	return ReviewFeedbackContextSnapshot{
		ReviewContextID: strings.TrimSpace(snapshot.ReviewContextID),
		SubjectType:     strings.TrimSpace(snapshot.SubjectType),
		SubjectID:       strings.TrimSpace(snapshot.SubjectID),
		SourceAgentID:   strings.TrimSpace(snapshot.SourceAgentID),
		Kind:            strings.TrimSpace(snapshot.Kind),
		DisplayName:     trimRunes(strings.TrimSpace(snapshot.DisplayName), maxReviewFeedbackMetadataDisplayNameRunes),
		HeadRef:         trimRunes(strings.TrimSpace(snapshot.HeadRef), maxReviewFeedbackMetadataRefRunes),
		BaseRef:         trimRunes(strings.TrimSpace(snapshot.BaseRef), maxReviewFeedbackMetadataRefRunes),
		ComparisonMode:  strings.TrimSpace(snapshot.ComparisonMode),
	}
}

func compactReviewFeedbackItemForMetadata(item ReviewFeedbackItem, bodyLimit int) ReviewFeedbackItem {
	status := NormalizeReviewFeedbackStatus(item.Status)
	if status == "" {
		status = ReviewFeedbackStatusOpen
	}
	return ReviewFeedbackItem{
		ID:                 strings.TrimSpace(item.ID),
		Type:               strings.TrimSpace(item.Type),
		Kind:               strings.TrimSpace(item.Kind),
		Path:               trimRunes(strings.TrimSpace(item.Path), maxReviewFeedbackMetadataPathRunes),
		OldPath:            trimRunes(strings.TrimSpace(item.OldPath), maxReviewFeedbackMetadataPathRunes),
		Side:               strings.TrimSpace(item.Side),
		OldLine:            copyInt64Ptr(item.OldLine),
		NewLine:            copyInt64Ptr(item.NewLine),
		StartLine:          copyInt64Ptr(item.StartLine),
		EndLine:            copyInt64Ptr(item.EndLine),
		HunkHeader:         trimRunes(strings.TrimSpace(item.HunkHeader), maxReviewFeedbackMetadataHunkHeaderRunes),
		AnchorFingerprint:  trimRunes(strings.TrimSpace(item.AnchorFingerprint), maxReviewFeedbackMetadataFingerprintRunes),
		CodePreview:        normalizeReviewFeedbackCodePreview(item.CodePreview, maxReviewFeedbackMetadataCodePreviewRunes),
		Body:               reviewFeedbackBodyForMetadata(item.Body, bodyLimit),
		Status:             status,
		CreatedByMemberID:  strings.TrimSpace(item.CreatedByMemberID),
		CreatedAt:          strings.TrimSpace(item.CreatedAt),
		ResolvedByMemberID: strings.TrimSpace(item.ResolvedByMemberID),
		ResolvedAt:         strings.TrimSpace(item.ResolvedAt),
	}
}

func reviewFeedbackBodyForMetadata(body string, limit int) string {
	body = strings.TrimSpace(stripLeadingReviewFeedbackQuoteBlock(body))
	if limit <= 0 {
		return ""
	}
	return trimRunes(body, limit)
}

func stripLeadingReviewFeedbackQuoteBlock(body string) string {
	trimmed := strings.TrimSpace(body)
	if trimmed == "" {
		return ""
	}
	lines := strings.Split(trimmed, "\n")
	index := 0
	sawQuote := false
	for index < len(lines) {
		line := strings.TrimRight(lines[index], "\r")
		trimmedLine := strings.TrimSpace(line)
		if trimmedLine == "" {
			if sawQuote {
				index++
				continue
			}
			break
		}
		if strings.HasPrefix(strings.TrimLeft(line, " \t"), ">") {
			sawQuote = true
			index++
			continue
		}
		break
	}
	if sawQuote && index < len(lines) {
		if remainder := strings.TrimSpace(strings.Join(lines[index:], "\n")); remainder != "" {
			return remainder
		}
	}
	return trimmed
}

func reviewFeedbackMetadataTooLarge(err error) bool {
	return err != nil && strings.Contains(err.Error(), fmt.Sprintf("metadata is too large (max %d bytes)", maxMessageMetadataBytes))
}

func NormalizeReviewFeedbackStatus(status string) string {
	switch strings.TrimSpace(status) {
	case ReviewFeedbackStatusOpen:
		return ReviewFeedbackStatusOpen
	case ReviewFeedbackStatusResolved:
		return ReviewFeedbackStatusResolved
	default:
		return ""
	}
}

func NormalizeReviewFeedbackDecision(decision string) string {
	switch strings.TrimSpace(decision) {
	case ReviewFeedbackDecisionApproved, "approve":
		return ReviewFeedbackDecisionApproved
	default:
		return ""
	}
}

func FormatReviewFeedbackMessage(payload ReviewFeedbackPayload) string {
	blocks := make([]string, 0, len(payload.Items))
	for _, item := range reviewFeedbackItemsForMessage(payload.Items) {
		body := reviewFeedbackMarkdownBody(item.Body)
		if body == "" {
			continue
		}
		if location := reviewFeedbackItemLocationText(item); location != "" {
			locationBlock := "> " + location
			if codePreview := normalizeReviewFeedbackCodePreview(item.CodePreview, maxReviewFeedbackMetadataCodePreviewRunes); codePreview != "" {
				for _, line := range strings.Split(codePreview, "\n") {
					locationBlock += "\n> " + line
				}
			}
			body = locationBlock + "\n\n" + body
		}
		blocks = append(blocks, strings.TrimSpace(body))
	}
	body := strings.TrimSpace(strings.Join(blocks, "\n\n---\n\n"))
	if decisionMessage := reviewFeedbackDecisionMessage(payload.Decision, payload.Locale); decisionMessage != "" {
		if body == "" {
			return decisionMessage
		}
		return decisionMessage + "\n\n" + body
	}
	return body
}

func reviewFeedbackItemsForMessage(items []ReviewFeedbackItem) []ReviewFeedbackItem {
	if len(items) <= 1 {
		return items
	}
	ordered := make([]ReviewFeedbackItem, 0, len(items))
	for _, item := range items {
		if reviewFeedbackItemIsGeneral(item) {
			ordered = append(ordered, item)
		}
	}
	for _, item := range items {
		if !reviewFeedbackItemIsGeneral(item) {
			ordered = append(ordered, item)
		}
	}
	return ordered
}

func reviewFeedbackItemIsGeneral(item ReviewFeedbackItem) bool {
	switch strings.TrimSpace(item.Type) {
	case "general":
		return true
	case "line":
		return false
	}
	switch strings.TrimSpace(item.Kind) {
	case "general":
		return true
	case "inline", "line":
		return false
	}
	return strings.TrimSpace(item.Path) == "" && item.OldLine == nil && item.NewLine == nil && item.StartLine == nil && item.EndLine == nil && len(item.Anchor) == 0
}

func ReviewFeedbackContributionSourceLabel(payload ReviewFeedbackPayload) string {
	return reviewFeedbackContextDisplayName(payload)
}

func reviewFeedbackDecisionMessage(decision string, locale string) string {
	if NormalizeReviewFeedbackDecision(decision) != ReviewFeedbackDecisionApproved {
		return ""
	}
	switch normalizeReviewFeedbackLocale(locale) {
	case "zh-CN":
		return "变更已批准"
	case "ja":
		return "変更を承認しました"
	default:
		return "Changes approved"
	}
}

func reviewFeedbackContextDisplayName(payload ReviewFeedbackPayload) string {
	return strings.TrimSpace(payload.Context.DisplayName)
}

func reviewFeedbackMarkdownBody(body string) string {
	body = strings.TrimSpace(body)
	if body == "" {
		return ""
	}
	quoteLines, remainder := splitLeadingReviewFeedbackQuoteBlock(body)
	if len(quoteLines) == 0 {
		return body
	}
	quote := reviewFeedbackMarkdownQuotePreview(quoteLines)
	if strings.TrimSpace(remainder) == "" {
		return quote
	}
	return strings.TrimSpace(quote + "\n\n" + strings.TrimSpace(remainder))
}

func splitLeadingReviewFeedbackQuoteBlock(body string) ([]string, string) {
	lines := strings.Split(body, "\n")
	quoteLines := make([]string, 0, len(lines))
	index := 0
	for index < len(lines) {
		line := strings.TrimRight(lines[index], "\r")
		trimmedLine := strings.TrimSpace(line)
		if strings.HasPrefix(strings.TrimLeft(line, " \t"), ">") {
			quoteLines = append(quoteLines, line)
			index++
			continue
		}
		if len(quoteLines) > 0 && trimmedLine == "" {
			index++
			if index < len(lines) && strings.HasPrefix(strings.TrimLeft(strings.TrimRight(lines[index], "\r"), " \t"), ">") {
				quoteLines = append(quoteLines, ">")
				continue
			}
			break
		}
		break
	}
	for index < len(lines) && strings.TrimSpace(lines[index]) == "" {
		index++
	}
	if len(quoteLines) == 0 {
		return nil, body
	}
	return quoteLines, strings.TrimSpace(strings.Join(lines[index:], "\n"))
}

func reviewFeedbackMarkdownQuotePreview(lines []string) string {
	quoted := make([]string, 0, len(lines))
	usedRunes := 0
	truncated := false
	for _, line := range lines {
		if len(quoted) >= maxReviewFeedbackMessageQuotePreviewLines {
			truncated = true
			break
		}
		line = strings.TrimRight(line, "\r")
		text := strings.TrimLeft(line, " \t")
		if strings.HasPrefix(text, ">") {
			text = strings.TrimSpace(strings.TrimPrefix(text, ">"))
		}
		remaining := maxReviewFeedbackMessageQuotePreviewRunes - usedRunes
		if remaining <= 0 {
			truncated = true
			break
		}
		runeCount := len([]rune(text))
		if runeCount > remaining {
			text = trimRunes(text, remaining)
			truncated = true
		}
		if text == "" {
			quoted = append(quoted, ">")
		} else {
			quoted = append(quoted, "> "+text)
		}
		usedRunes += len([]rune(text)) + 1
		if truncated {
			break
		}
	}
	if truncated {
		quoted = append(quoted, "> …")
	}
	return strings.Join(quoted, "\n")
}

func normalizeReviewFeedbackCodePreview(value string, limit int) string {
	value = strings.TrimSpace(strings.ReplaceAll(strings.ReplaceAll(value, "\r\n", "\n"), "\r", "\n"))
	if value == "" || limit <= 0 {
		return ""
	}
	lines := strings.Split(value, "\n")
	normalized := make([]string, 0, len(lines))
	usedRunes := 0
	truncated := false
	for _, line := range lines {
		if len(normalized) >= maxReviewFeedbackCodePreviewLines {
			truncated = true
			break
		}
		line = strings.TrimSpace(line)
		remaining := limit - usedRunes
		if remaining <= 0 {
			truncated = true
			break
		}
		runeCount := len([]rune(line))
		if runeCount > remaining {
			line = trimRunes(line, remaining)
			truncated = true
		}
		normalized = append(normalized, line)
		usedRunes += len([]rune(line)) + 1
		if truncated {
			break
		}
	}
	if len(normalized) == 0 {
		return ""
	}
	if truncated {
		candidate := strings.Join(append(append([]string{}, normalized...), "…"), "\n")
		if len([]rune(candidate)) <= limit {
			normalized = append(normalized, "…")
		}
	}
	return strings.TrimSpace(strings.Join(normalized, "\n"))
}

func normalizeReviewFeedbackLocale(locale string) string {
	normalized := strings.TrimSpace(locale)
	if normalized == "" {
		return "en"
	}
	if comma := strings.Index(normalized, ","); comma >= 0 {
		normalized = normalized[:comma]
	}
	if semicolon := strings.Index(normalized, ";"); semicolon >= 0 {
		normalized = normalized[:semicolon]
	}
	normalized = strings.TrimSpace(normalized)
	lower := strings.ToLower(normalized)
	switch {
	case lower == "zh-cn" || strings.HasPrefix(lower, "zh-hans") || lower == "zh":
		return "zh-CN"
	case lower == "ja" || strings.HasPrefix(lower, "ja-"):
		return "ja"
	default:
		return "en"
	}
}

func reviewFeedbackItemLocation(item ReviewFeedbackItem) string {
	if location := reviewFeedbackItemLocationText(item); location != "" {
		return "`" + location + "`"
	}
	return ""
}

func reviewFeedbackItemLocationText(item ReviewFeedbackItem) string {
	path := strings.TrimSpace(item.Path)
	if path == "" {
		return ""
	}
	if startLine, endLine, ok := reviewFeedbackItemRange(item); ok {
		if endLine > startLine {
			return fmt.Sprintf("%s:%d-%d", path, startLine, endLine)
		}
		return fmt.Sprintf("%s:%d", path, startLine)
	}
	if strings.TrimSpace(item.Side) == "old" && item.OldLine != nil {
		return fmt.Sprintf("%s:%d", path, *item.OldLine)
	}
	if item.NewLine != nil {
		return fmt.Sprintf("%s:%d", path, *item.NewLine)
	}
	if item.OldLine != nil {
		return fmt.Sprintf("%s:%d", path, *item.OldLine)
	}
	if line, ok := numericAnchorValue(item.Anchor, "line"); ok {
		return fmt.Sprintf("%s:%d", path, line)
	}
	if line, ok := numericAnchorValue(item.Anchor, "newLine"); ok {
		return fmt.Sprintf("%s:%d", path, line)
	}
	if line, ok := numericAnchorValue(item.Anchor, "oldLine"); ok {
		return fmt.Sprintf("%s:%d", path, line)
	}
	return path
}

func reviewFeedbackItemRange(item ReviewFeedbackItem) (int64, int64, bool) {
	startLine := copyInt64Ptr(item.StartLine)
	endLine := copyInt64Ptr(item.EndLine)
	startLine, endLine = normalizeReviewFeedbackLineRange(startLine, endLine)
	if startLine == nil {
		return 0, 0, false
	}
	return *startLine, *endLine, true
}

func normalizeReviewFeedbackLineRange(startLine *int64, endLine *int64) (*int64, *int64) {
	if startLine == nil && endLine == nil {
		return nil, nil
	}
	if startLine == nil {
		startLine = copyInt64Ptr(endLine)
	}
	if endLine == nil {
		endLine = copyInt64Ptr(startLine)
	}
	if startLine != nil && endLine != nil && *startLine > *endLine {
		start := *endLine
		end := *startLine
		return &start, &end
	}
	return startLine, endLine
}

func normalizeReviewFeedbackSide(side string, oldLine *int64, newLine *int64) string {
	switch strings.TrimSpace(side) {
	case "old":
		return "old"
	case "new":
		return "new"
	}
	if oldLine != nil && newLine == nil {
		return "old"
	}
	if newLine != nil {
		return "new"
	}
	return ""
}

func reviewFeedbackTypeFromLegacyKind(kind string, path string, anchor map[string]any, oldLine *int64, newLine *int64, startLine *int64, endLine *int64) string {
	switch strings.TrimSpace(kind) {
	case "line":
		return "line"
	case "general":
		return "general"
	case "inline":
		return "line"
	}
	if strings.TrimSpace(path) != "" || len(anchor) > 0 || oldLine != nil || newLine != nil || startLine != nil || endLine != nil {
		return "line"
	}
	return "general"
}

func reviewFeedbackLegacyKind(itemType string) string {
	switch strings.TrimSpace(itemType) {
	case "line":
		return "inline"
	case "general":
		return "general"
	default:
		return strings.TrimSpace(itemType)
	}
}

func copyInt64Ptr(value *int64) *int64 {
	if value == nil {
		return nil
	}
	copied := *value
	return &copied
}

func firstNumericAnchorValuePtr(anchor map[string]any, keys ...string) *int64 {
	for _, key := range keys {
		if value := numericAnchorValuePtr(anchor, key); value != nil {
			return value
		}
	}
	return nil
}

func numericAnchorValuePtr(anchor map[string]any, key string) *int64 {
	line, ok := numericAnchorValue(anchor, key)
	if !ok {
		return nil
	}
	return &line
}

func numericAnchorValue(anchor map[string]any, key string) (int64, bool) {
	if len(anchor) == 0 {
		return 0, false
	}
	raw, ok := anchor[key]
	if !ok {
		return 0, false
	}
	switch value := raw.(type) {
	case int:
		return int64(value), true
	case int64:
		return value, true
	case float64:
		if value == float64(int64(value)) {
			return int64(value), true
		}
	}
	return 0, false
}

func reviewFeedbackPayloadToMap(payload ReviewFeedbackPayload) (map[string]any, error) {
	encoded, err := json.Marshal(payload)
	if err != nil {
		return nil, fmt.Errorf("encode review feedback payload: %w", err)
	}
	var value map[string]any
	if err := json.Unmarshal(encoded, &value); err != nil {
		return nil, fmt.Errorf("decode review feedback payload: %w", err)
	}
	return value, nil
}

func copyMap(input map[string]any) map[string]any {
	if len(input) == 0 {
		return nil
	}
	copied := make(map[string]any, len(input))
	keys := make([]string, 0, len(input))
	for key := range input {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		trimmedKey := strings.TrimSpace(key)
		if trimmedKey == "" {
			continue
		}
		copied[trimmedKey] = input[key]
	}
	if len(copied) == 0 {
		return nil
	}
	return copied
}

func trimRunes(value string, limit int) string {
	if limit <= 0 {
		return value
	}
	runes := []rune(value)
	if len(runes) <= limit {
		return value
	}
	return string(runes[:limit])
}
