// Package runtimecontrol defines daemon runtime-control contracts that are
// shared by API domains without importing the daemon route package.
package runtimecontrol

import (
	"context"
	"encoding/json"
	"errors"

	"github.com/tidbcloud/AgentTeam/internal/store/agent"
)

const (
	WorkspaceTargetPathCapability           = "workspace:target-path"
	WorkspaceTargetPathDaemonUpgradeMessage = "This machine's agentteam daemon is too old to browse review target files. Upgrade agentteam daemon and try again."
	AgentPermissionModeCapability           = "agent:permission-mode"
	AgentPermissionModeDaemonUpgradeMessage = "This machine's agentteam daemon is too old to enforce agent workspace-write permissions. Upgrade agentteam daemon and try again."
	AgentMaaSDirectCapability               = "agent:maas-direct"
	AgentMaaSDirectDaemonUpgradeMessage     = "This machine's agentteam daemon is too old to use direct agentteam-maas runtime capabilities. Upgrade agentteam daemon and try again."
	AgentRuntimeSessionAuthorityCapability  = "agent:runtime-session-authority"
	ReviewSnapshotDaemonUpgradeMessage      = "This machine's agentteam daemon does not support review snapshots yet."
	ReviewFingerprintDaemonUpgradeMessage   = "This machine's agentteam daemon does not support review fingerprints yet."
	AgentLocalSessionResumeCapability       = "agent:local-session-resume"
	AgentVisibleReplyContractCapability     = "agent:visible-reply-contract-v1"

	// ReviewTargetUnsupportedMessage is the stable, path-free user-facing
	// message returned when a Review RPC targets a repo whose tree contains
	// a path the diff engine cannot handle (currently: paths with embedded
	// control characters such as newlines, which go-git's merkletrie
	// validator rejects outright). The text is deliberately fixed: the
	// original go-git error includes the offending path verbatim, so the
	// workspace layer (workspace.ErrReviewControlCharPath) drops it and
	// only this string crosses the wire.
	ReviewTargetUnsupportedMessage = "This repository contains a file path the review engine does not support (e.g. a name with an embedded newline). The review is unavailable for this repository."
)

const (
	// ReviewTargetUnsupportedOutcomeCode is the stable wire outcome string
	// the Desktop remote-endpoint handlers emit (in the Outcome field of
	// remoteRequestResultMsg) when workspace.ErrReviewControlCharPath fires.
	// Hub-side remoteEndpointResultError maps this string back to the typed
	// ErrReviewTargetUnsupported sentinel. Stable across daemon versions;
	// never include repo-specific data.
	ReviewTargetUnsupportedOutcomeCode = "review_target_unsupported"

	// ReviewTargetUnsupportedLegacyErrorCode is the stable wire error code
	// the Desktop legacy handlers emit (in the ErrorCode field of the
	// agentReview*ResultMsg structs) for the same condition. Hub-side
	// handleSessionLinkAgentReview*ResultCommand maps this code back to the
	// typed sentinel. The legacy wire path has no Outcome field, so a new
	// ErrorCode field carries the signal.
	ReviewTargetUnsupportedLegacyErrorCode = "review_target_unsupported"

	// MachineDirectoryInvalidOutcome is the stable wire outcome the Desktop
	// machine.directory.list handler emits when the requested path or cursor
	// is malformed. Hub-side mappers reconstruct the typed
	// ErrMachineDirectoryInvalid sentinel so the HTTP layer can answer 400
	// without leaking the offending path.
	MachineDirectoryInvalidOutcome = "machine_directory_invalid"

	// MachineDirectoryUnreadableOutcome is the stable wire outcome the
	// Desktop handler emits when the resolved directory cannot be opened,
	// stat, read, or transcends the byte-budget. Hub-side mappers map this
	// to ErrMachineDirectoryUnreadable so the HTTP layer answers 422
	// without leaking the offending path.
	MachineDirectoryUnreadableOutcome = "machine_directory_unreadable"

	// MachineDashboardInvalidOutcome is the stable wire outcome the Desktop
	// machine.dashboard handler emits when the request shape, window, or agent
	// selection is invalid. Hub-side mappers reconstruct the typed
	// ErrMachineDashboardInvalid sentinel so the HTTP layer can answer 400.
	MachineDashboardInvalidOutcome = "machine_dashboard_invalid"
)

var (
	// ErrMachineWorkspaceListUnavailable means the daemon store cannot list
	// owner workspaces for the connected machine.
	ErrMachineWorkspaceListUnavailable = errors.New("machine workspace list is unavailable")
	// ErrMachineOffline means the target machine currently has no active
	// daemon websocket.
	ErrMachineOffline = errors.New("machine is offline")
	// ErrAgentManuallyStopped means explicit Human intent currently prevents
	// automatic runtime startup.
	ErrAgentManuallyStopped = errors.New("agent is manually stopped")
	// ErrAgentStillRunning means the daemon has not yet confirmed that the
	// previous runtime exited, so a replacement start would overlap.
	ErrAgentStillRunning = errors.New("agent is still running")
	// ErrDaemonWorkspaceTargetPathUpgradeRequired means the connected daemon
	// cannot read files from an explicit target path.
	ErrDaemonWorkspaceTargetPathUpgradeRequired = errors.New(WorkspaceTargetPathDaemonUpgradeMessage)
	// ErrDaemonPermissionModeUpgradeRequired means the connected daemon cannot
	// enforce agent permission modes and must not receive workspace-write agents.
	ErrDaemonPermissionModeUpgradeRequired = errors.New(AgentPermissionModeDaemonUpgradeMessage)
	// ErrDaemonMaaSDirectUpgradeRequired means the connected daemon cannot
	// accept direct agentteam-maas runtime capability configs.
	ErrDaemonMaaSDirectUpgradeRequired = errors.New(AgentMaaSDirectDaemonUpgradeMessage)
	// ErrDaemonReviewSnapshotUnavailable lets callers fall back to the legacy
	// summary/files RPCs during a rolling desktop upgrade.
	ErrDaemonReviewSnapshotUnavailable = errors.New(ReviewSnapshotDaemonUpgradeMessage)
	// ErrDaemonReviewFingerprintUnavailable lets callers fall back to the
	// snapshot RPC during a rolling desktop upgrade. The fingerprint endpoint
	// is an optimization layered on top of snapshot, so the frontend must
	// always be able to degrade gracefully.
	ErrDaemonReviewFingerprintUnavailable = errors.New(ReviewFingerprintDaemonUpgradeMessage)
	// ErrReviewTargetUnsupported means the Review RPC reached a repo whose
	// tree contains a path the diff engine cannot handle (control-character
	// filenames such as embedded newlines, which go-git's merkletrie
	// validator rejects outright). This is a product limitation, not a
	// transient failure: callers SHOULD surface a stable "unsupported"
	// response (HTTP 422) so the frontend can distinguish it from 500 and
	// avoid retry storms. The underlying workspace sentinel
	// (workspace.ErrReviewControlCharPath) is dropped at the wire boundary;
	// Hub-side mappers reconstruct this typed sentinel from the stable
	// outcome / ErrorCode strings so channel/webagent can errors.Is it.
	ErrReviewTargetUnsupported = errors.New(ReviewTargetUnsupportedMessage)

	// ErrMachineDirectoryInvalid means the requested machine directory path
	// or cursor was malformed (non-absolute path, missing directory,
	// non-canonical cursor syntax). It is a stable product signal: callers
	// SHOULD surface it as HTTP 400 so the frontend can distinguish a
	// caller-side mistake from server-side failure. The underlying path is
	// never attached; Hub-side mappers reconstruct this typed sentinel from
	// the stable wire outcome string.
	ErrMachineDirectoryInvalid = errors.New("machine directory path is invalid")

	// ErrMachineDirectoryUnreadable means the resolved directory could not
	// be opened, stat, read, or transcended the per-page byte budget. It is
	// a stable product signal: callers SHOULD surface it as HTTP 422 so the
	// frontend can distinguish "the desktop cannot read this right now"
	// from a caller mistake. The underlying path and OS error text are
	// never attached.
	ErrMachineDirectoryUnreadable = errors.New("machine directory cannot be read")

	// ErrMachineDirectoryUnsupported means the connected Desktop does not
	// advertise the machine.directory.list endpoint. This is the only
	// signal that may drive the Web UI's manual path fallback. Callers
	// SHOULD surface it as HTTP 501 so the frontend disables browsing and
	// shows the manual path input. The Hub returns it BEFORE any assignment
	// is sent so the Desktop observes no request.
	ErrMachineDirectoryUnsupported = errors.New("machine directory browsing is unsupported")

	// ErrMachineDirectoryUnavailable means the Desktop is offline, the
	// elastic carrier could not be established, the request deadline
	// fired, or an unexpected error escaped the route. Callers SHOULD
	// surface it as HTTP 503. The underlying raw error is never attached.
	ErrMachineDirectoryUnavailable = errors.New("machine directory browsing is unavailable")

	// ErrMachineDirectoryBusy means the Desktop's elastic carrier or
	// session-link data plane is at capacity. Callers SHOULD surface it as
	// HTTP 503 with a distinguishable retry signal. The underlying raw
	// error is never attached.
	ErrMachineDirectoryBusy = errors.New("machine directory browsing is busy")

	// ErrMachineDashboardInvalid means the requested dashboard window or agent
	// selection is malformed. It is a stable product signal: callers SHOULD
	// surface it as HTTP 400.
	ErrMachineDashboardInvalid = errors.New("machine dashboard request is invalid")

	// ErrMachineDashboardUnsupported means the connected Desktop does not
	// advertise the machine.dashboard endpoint. Callers SHOULD surface it as
	// HTTP 501 so the Web UI can prompt for a AgentTeam Desktop upgrade.
	ErrMachineDashboardUnsupported = errors.New("machine dashboard is unsupported")

	// ErrMachineDashboardUnavailable means the Desktop is offline or cannot
	// currently answer dashboard metrics. Callers SHOULD surface it as HTTP 503.
	ErrMachineDashboardUnavailable = errors.New("machine dashboard is unavailable")

	// ErrMachineSettingsInvalid means the requested machine settings update
	// payload was malformed or rejected by the daemon. Stable product signal
	// surfaced as HTTP 400.
	ErrMachineSettingsInvalid = errors.New("machine settings request is invalid")

	// ErrMachineSettingsUnsupported means the connected Desktop does not
	// advertise the machine.settings endpoint. Callers SHOULD surface it as
	// HTTP 501 so the Web UI can prompt for a AgentTeam Desktop upgrade.
	ErrMachineSettingsUnsupported = errors.New("machine settings are unsupported")

	// ErrMachineSettingsUnavailable means the Desktop is offline or cannot
	// currently answer machine settings. Callers SHOULD surface it as HTTP 503.
	ErrMachineSettingsUnavailable = errors.New("machine settings are unavailable")

	// ErrMachineRuntimesInvalid means the requested machine runtimes payload
	// was malformed or rejected. Stable product signal surfaced as HTTP 400.
	ErrMachineRuntimesInvalid = errors.New("machine runtimes request is invalid")

	// ErrMachineRuntimesUnsupported means the connected Desktop does not
	// advertise the machine.runtimes.* endpoints. HTTP 501.
	ErrMachineRuntimesUnsupported = errors.New("machine runtimes endpoint is unsupported")

	// ErrMachineRuntimesUnavailable means the Desktop is offline or cannot
	// currently answer machine runtimes. HTTP 503.
	ErrMachineRuntimesUnavailable = errors.New("machine runtimes endpoint is unavailable")

	// ErrMachineMCPServersInvalid means the requested machine MCP server
	// payload was malformed or rejected. Stable product signal, HTTP 400.
	ErrMachineMCPServersInvalid = errors.New("machine mcp server request is invalid")

	// ErrMachineMCPServersUnsupported means the connected Desktop does not
	// advertise the machine.mcp_servers.* endpoints. HTTP 501.
	ErrMachineMCPServersUnsupported = errors.New("machine mcp servers endpoint is unsupported")

	// ErrMachineMCPServersUnavailable means the Desktop is offline or cannot
	// currently answer machine MCP server requests. HTTP 503.
	ErrMachineMCPServersUnavailable = errors.New("machine mcp servers endpoint is unavailable")
)

// MachineDashboardWindow is the dashboard aggregation time window.
type MachineDashboardWindow string

const (
	MachineDashboardWindow6Hours  MachineDashboardWindow = "6h"
	MachineDashboardWindow12Hours MachineDashboardWindow = "12h"
	MachineDashboardWindow1Day    MachineDashboardWindow = "1d"
	MachineDashboardWindow3Days   MachineDashboardWindow = "3d"
	MachineDashboardWindow1Week   MachineDashboardWindow = "1w"
)

// MachineDashboardRequest scopes one machine dashboard metrics query.
type MachineDashboardRequest struct {
	WorkspaceID string
	MachineID   string
	Window      MachineDashboardWindow
	AgentIDs    []string
}

// MachineDashboardResponse is the browser-facing machine dashboard contract.
type MachineDashboardResponse struct {
	MachineID      string                         `json:"machineId"`
	WorkspaceID    string                         `json:"workspaceId"`
	Window         string                         `json:"window"`
	AgentCount     int                            `json:"agentCount"`
	ActiveAgents   int                            `json:"activeAgents"`
	MessageSummary MachineDashboardMessageSummary `json:"messageSummary"`
	MessageTrend   MachineDashboardMessageTrend   `json:"messageTrend"`
	MessageBar     MachineDashboardBarSeries      `json:"messageBar"`
	TokenSummary   MachineDashboardTokenSummary   `json:"tokenSummary"`
	TokenTrend     MachineDashboardTokenTrend     `json:"tokenTrend"`
	InputTokenBar  MachineDashboardBarSeries      `json:"inputTokenBar"`
	CachedTokenBar MachineDashboardBarSeries      `json:"cachedTokenBar"`
	OutputTokenBar MachineDashboardBarSeries      `json:"outputTokenBar"`
	TokenSources   MachineTokenSources            `json:"tokenSources"`
	Agents         []MachineDashboardAgent        `json:"agents"`
}

// MachineTokenSources is the bounded per-source token trend collected by the
// connected Desktop daemon.
type MachineTokenSources struct {
	Available bool                    `json:"available"`
	Trend     MachineTokenSourceTrend `json:"trend"`
}

type MachineTokenSourceTrendSeries struct {
	Key        string `json:"key"`
	Label      string `json:"label"`
	SourceKind string `json:"sourceKind"`
}

type MachineTokenSourceTrend struct {
	Series []MachineTokenSourceTrendSeries `json:"series"`
	Data   []MachineTokenSourceTrendPoint  `json:"data"`
}

type MachineTokenSourceTrendPoint struct {
	Timestamp int64             `json:"timestamp"`
	Values    map[string]uint64 `json:"values"`
}

type MachineDashboardMessageSummary struct {
	Received uint64 `json:"received"`
	Sent     uint64 `json:"sent"`
	Total    uint64 `json:"total"`
}

type MachineDashboardDirectionalPoint struct {
	Timestamp int64  `json:"timestamp"`
	Received  uint64 `json:"received"`
	Sent      uint64 `json:"sent"`
}

type MachineDashboardMessageTrend struct {
	Interval int                                `json:"interval"`
	Data     []MachineDashboardDirectionalPoint `json:"data"`
}

type MachineDashboardTokenSummary struct {
	Count  uint64 `json:"count"`
	Input  uint64 `json:"input"`
	Cached uint64 `json:"cached"`
	Output uint64 `json:"output"`
	Total  uint64 `json:"total"`
}

type MachineDashboardTokenPoint struct {
	Timestamp int64  `json:"timestamp"`
	Count     uint64 `json:"count"`
	Input     uint64 `json:"input"`
	Cached    uint64 `json:"cached"`
	Output    uint64 `json:"output"`
}

type MachineDashboardTokenTrend struct {
	Interval int                          `json:"interval"`
	Data     []MachineDashboardTokenPoint `json:"data"`
}

type MachineDashboardBarSeriesEntry struct {
	Key   string `json:"key"`
	Label string `json:"label"`
}

type MachineDashboardBarSeriesPoint struct {
	Timestamp int64             `json:"timestamp"`
	Values    map[string]uint64 `json:"values"`
}

type MachineDashboardBarSeries struct {
	Interval int                              `json:"interval"`
	Series   []MachineDashboardBarSeriesEntry `json:"series"`
	Data     []MachineDashboardBarSeriesPoint `json:"data"`
}

type MachineDashboardAgent struct {
	AgentID  string `json:"agentId"`
	Name     string `json:"name"`
	Runtime  string `json:"runtime"`
	Active   bool   `json:"active"`
	Received uint64 `json:"received"`
	Sent     uint64 `json:"sent"`
	Input    uint64 `json:"input"`
	Cached   uint64 `json:"cached"`
	Output   uint64 `json:"output"`
	Total    uint64 `json:"total"`
}

// AgentTrajectoryItem is one daemon trajectory entry forwarded to browser
// runtime surfaces.
type AgentTrajectoryItem struct {
	AgentID   string          `json:"agentId"`
	Entry     json.RawMessage `json:"entry"`
	Timestamp int64           `json:"timestamp"`
}

// MachineWorkspace describes one workspace visible to a connected machine.
type MachineWorkspace struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Slug string `json:"slug,omitempty"`
}

// MachineWorkspaceLister is the runtime-control store contract for listing
// owner workspaces available to the authenticated machine.
type MachineWorkspaceLister interface {
	ListMachineOwnerWorkspaces(ctx context.Context, machine agent.MachineIdentity) ([]MachineWorkspace, error)
}

// RestoreAgentRecoveryPointRequest asks a daemon to restore a prepared runtime
// recovery point.
type RestoreAgentRecoveryPointRequest struct {
	AgentID     string
	Runtime     string
	Model       string
	SessionID   string
	Strategy    string
	RecoveryRef string
}

// RecoveryPointGCRequest identifies one prepared recovery point that the
// daemon may garbage collect.
type RecoveryPointGCRequest struct {
	Strategy    string
	RecoveryRef string
}
