package runtime

import (
	"context"
	"encoding/json"
	"strings"
)

const maxRefNameBytes = 255

type knowledgeRefArguments struct {
	RootAlias string `json:"rootAlias"`
	Ref       string `json:"ref"`
	RefType   string `json:"refType"`
}

type knowledgeSourceBranchChangeArguments struct {
	BranchRootAlias string `json:"branchRootAlias"`
}

type explorationRefResult struct {
	OK        bool   `json:"ok"`
	RootAlias string `json:"rootAlias,omitempty"`
	Ref       string `json:"ref,omitempty"`
	RefType   string `json:"refType,omitempty"`
	Error     string `json:"error,omitempty"`
}

func validateRefArguments(fields map[string]json.RawMessage, arguments *knowledgeRefArguments) error {
	if fieldMissingOrNull(fields, "rootAlias") {
		return explorationToolValidationError(knowledgeRefFunction, repairIssueRefRootRequired)
	}
	if fieldMissingOrNull(fields, "ref") {
		return explorationToolValidationError(knowledgeRefFunction, repairIssueRefNameRequired)
	}
	if fieldMissingOrNull(fields, "refType") {
		return explorationToolValidationError(knowledgeRefFunction, repairIssueRefTypeRequired)
	}
	arguments.RootAlias = strings.TrimSpace(arguments.RootAlias)
	arguments.Ref = strings.TrimSpace(arguments.Ref)
	arguments.RefType = strings.TrimSpace(arguments.RefType)
	if len(arguments.RootAlias) > maxReferenceBytes || !rootReferencePattern.MatchString(arguments.RootAlias) {
		return explorationToolValidationError(knowledgeRefFunction, repairIssueRefRootInvalid)
	}
	if arguments.Ref == "" || len(arguments.Ref) > maxRefNameBytes {
		return explorationToolValidationError(knowledgeRefFunction, repairIssueRefNameInvalid)
	}
	if arguments.RefType != RefKindBranch && arguments.RefType != RefKindTag {
		return explorationToolValidationError(knowledgeRefFunction, repairIssueRefTypeInvalid)
	}
	return nil
}

func (runtime *Runtime) explorationRef(
	ctx context.Context,
	repository *ExecutionRepository,
	refs *explorationReferences,
	arguments knowledgeRefArguments,
	usage *ToolUsage,
) (explorationRefResult, error) {
	handleID, ok := refs.resolveRoot(arguments.RootAlias)
	if !ok {
		return explorationRefResult{}, handleRejected()
	}
	usage.RefCalls++
	snapshot, err := repository.OpenRef(ctx, handleID, arguments.Ref, arguments.RefType)
	if err != nil {
		return explorationRefResult{}, err
	}
	alias := refs.addRefRoot(snapshot, arguments.RootAlias)
	return explorationRefResult{
		OK: true, RootAlias: alias, Ref: snapshot.TargetRef, RefType: snapshot.RefKind,
	}, nil
}
