package planmanage

import (
	"errors"

	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
)

// ErrorCode is the stable machine-readable classification exposed by the Plan
// API. Messages remain human-readable and may evolve.
type ErrorCode string

const (
	ErrorCodeInvalidRequest      ErrorCode = "plan_invalid_request"
	ErrorCodeNotFound            ErrorCode = "plan_not_found"
	ErrorCodeRevisionConflict    ErrorCode = "plan_revision_conflict"
	ErrorCodeDraftConflict       ErrorCode = "plan_draft_revision_conflict"
	ErrorCodeIdempotencyConflict ErrorCode = "plan_idempotency_conflict"
	ErrorCodeArchived            ErrorCode = "plan_archived"
	ErrorCodeCompileFailed       ErrorCode = "plan_compile_failed"
	ErrorCodeConflict            ErrorCode = "plan_conflict"
)

// RequestError preserves an HTTP-classifiable error kind, a stable domain
// code, and optional structured details without teaching the root API package
// about Plan semantics. Message is always reviewed stable public prose;
// Cause, when set, retains the originating repository error exclusively for
// logs and wrapping and is never projected as public text.
type RequestError struct {
	Kind    error
	Code    ErrorCode
	Message string
	Details any
	Cause   error
}

// RevisionConflictDetails gives clients the exact resource and revision they
// must reload after an optimistic-concurrency failure. Resource values are
// stable API identifiers rather than persistence-layer labels.
type RevisionConflictDetails struct {
	Resource         string `json:"resource"`
	ExpectedRevision int64  `json:"expectedRevision"`
	CurrentRevision  int64  `json:"currentRevision"`
}

func (err RequestError) Error() string { return err.Message }

// Unwrap returns the declared store kind only. The declared Kind is
// authoritative for public status/code classification: the internal Cause is
// preserved separately (InternalCause) for logs and structured logging and
// never participates in the errors.Is/As graph consumed by publicerr, so an
// internal cause that itself carries a store sentinel can never override the
// declared kind.
func (err RequestError) Unwrap() error { return err.Kind }

// InternalCause returns the originating repository error retained exclusively
// for logs and wrapping, or nil. It is deliberately not part of the Unwrap
// chain.
func (err RequestError) InternalCause() error { return err.Cause }

// WorkflowPublicError implements publicerr.DomainError so the centralized
// Workflow error classifier can extract the stable code and structured details
// without importing this domain package.
func (err RequestError) WorkflowPublicError() (string, any) {
	return string(err.Code), err.Details
}

// ErrorDetails returns structured response details for a domain error.
func ErrorDetails(err error) (ErrorCode, any, bool) {
	var requestError RequestError
	if !errors.As(err, &requestError) {
		return "", nil, false
	}
	return requestError.Code, requestError.Details, true
}

func invalidRequest(message string) error {
	return RequestError{Kind: storeerr.ErrBadRequest, Code: ErrorCodeInvalidRequest, Message: message}
}

func revisionConflict(message string, expectedRevision int64, currentRevision int64, cause error) error {
	return RequestError{
		Kind:    storeerr.ErrConflict,
		Code:    ErrorCodeRevisionConflict,
		Message: message,
		Details: RevisionConflictDetails{
			Resource:         "plan",
			ExpectedRevision: expectedRevision,
			CurrentRevision:  currentRevision,
		},
		Cause: cause,
	}
}

func draftConflict(message string, expectedRevision int64, currentRevision int64, cause error) error {
	return RequestError{
		Kind:    storeerr.ErrConflict,
		Code:    ErrorCodeDraftConflict,
		Message: message,
		Details: RevisionConflictDetails{
			Resource:         "planDraft",
			ExpectedRevision: expectedRevision,
			CurrentRevision:  currentRevision,
		},
		Cause: cause,
	}
}

func compileFailed(diagnostics []plan.Diagnostic) error {
	return RequestError{
		Kind:    storeerr.ErrUnprocessable,
		Code:    ErrorCodeCompileFailed,
		Message: "Plan draft did not compile",
		Details: map[string]any{"diagnostics": diagnostics},
	}
}
