syntax = "proto3";

package heddle.v1;

import "google/protobuf/timestamp.proto";
import "heddle/v1/common.proto";

service ContentService {
  rpc GetRefs(GetRefsRequest) returns (GetRefsResponse);
  rpc ListStates(ListStatesRequest) returns (ListStatesResponse);
  rpc GetState(GetStateRequest) returns (StateSummary);
  rpc GetBlame(GetBlameRequest) returns (GetBlameResponse);
  rpc ListProvenanceSummaries(ListProvenanceSummariesRequest) returns (ListProvenanceSummariesResponse);
  rpc GetTree(GetTreeRequest) returns (TreeResponse);
  rpc GetBlob(GetBlobRequest) returns (BlobResponse);
  rpc GetCompare(GetCompareRequest) returns (CompareResponse);
  rpc GetDiff(GetStateRequest) returns (DiffResponse);
  // Walk recent history and aggregate semantic-change events to surface
  // the files or functions with the most activity.
  rpc GetSemanticHotSpots(GetSemanticHotSpotsRequest) returns (GetSemanticHotSpotsResponse);
  rpc ListActions(GetStateRequest) returns (ActionsResponse);
  rpc ListContext(ListContextRequest) returns (ListContextResponse);
  rpc GetContextHistory(GetContextHistoryRequest) returns (GetContextHistoryResponse);
  rpc ListContextSuggestions(ListContextSuggestionsRequest) returns (ListContextSuggestionsResponse);
  rpc SetContext(SetContextRequest) returns (SetContextResponse);
  rpc ReviseContext(ReviseContextRequest) returns (ReviseContextResponse);
  rpc SupersedeContext(SupersedeContextRequest) returns (SupersedeContextResponse);
}

message GetRefsRequest {
  string repo_path = 1;
}

message GetRefsResponse {
  string head = 1;
  repeated RefEntry threads = 2;
  repeated string markers = 3;
}

message ListStatesRequest {
  string repo_path = 1;
  string ref = 2;
  uint32 limit = 3;
}

message GetStateRequest {
  string repo_path = 1;
  string id = 2;
}

message GetBlameRequest {
  string repo_path = 1;
  string ref = 2;
  string path = 3;
}

message ListProvenanceSummariesRequest {
  string repo_path = 1;
  string ref = 2;
  repeated string paths = 3;
}

message GetTreeRequest {
  string repo_path = 1;
  string ref = 2;
  string path = 3;
}

message TreeEntry {
  string name = 1;
  string kind = 2;
  bytes hash = 3;
}

message TreeResponse {
  bytes state_id = 1;
  string path = 2;
  repeated TreeEntry entries = 3;
}

message GetBlobRequest {
  string repo_path = 1;
  string ref = 2;
  string path = 3;
}

message GetCompareRequest {
  string repo_path = 1;
  string from = 2;
  string to = 3;
  bool include_semantic = 4;
}

message BlobResponse {
  string path = 1;
  bytes state_id = 2;
  uint64 size = 3;
  bool is_binary = 4;
  string content = 5;
}

message DiffSpan {
  uint32 start = 1; // byte offset into content
  uint32 end = 2; // byte offset (exclusive)
  bool changed = 3; // true = this span was added/removed
}

message DiffLine {
  string kind = 1;
  string content = 2;
  repeated DiffSpan word_changes = 3;
  optional string moved_counterpart_path = 4;
  optional uint32 moved_counterpart_line = 5;
}

message DiffHunk {
  uint32 old_start = 1;
  uint32 old_lines = 2;
  uint32 new_start = 3;
  uint32 new_lines = 4;
  repeated DiffLine lines = 5;
}

message FileDiff {
  string path = 1;
  string kind = 2;
  repeated DiffHunk hunks = 3;
  // Semantic classification of the modification (e.g. "Logic", "FormattingOnly", "ImportsOnly").
  string classification = 4;
  // Review importance (e.g. "High", "Medium", "Low", "Noise").
  string importance = 5;
}

message DiffResponse {
  bytes state_id = 1;
  bytes parent_id = 2;
  bool has_parent = 3;
  repeated FileDiff files = 4;
}

message CompareSummary {
  uint32 added = 1;
  uint32 modified = 2;
  uint32 deleted = 3;
  uint32 renamed = 4;
  uint32 total = 5;
}

message CompareResponse {
  bytes from_state_id = 1;
  bytes to_state_id = 2;
  repeated FileDiff files = 3;
  repeated string semantic_changes_json = 4;
  CompareSummary summary = 5;
}

// Hot-spot aggregation: walk first-parent history starting from
// `from_state` (or HEAD), aggregate semantic-change events across the
// last `limit_states` pairs, and rank by event count. Pairs with the
// CLI's `heddle semantic hot` and the semantic crate's
// `analyze_hot_spots` function — same primitives, no per-host glue.
message GetSemanticHotSpotsRequest {
  string repo_path = 1;
  // State to start the walk from. Empty = HEAD.
  string from_state = 2;
  // Walk at most this many state pairs. Default 200.
  uint32 limit_states = 3;
  // What to bucket on. "file" or "function" (case-insensitive).
  // Default "file".
  string group_by = 4;
  // Restrict to events of these kinds (e.g. "signature_changed",
  // "function_extracted"). Empty list = no filter. Spelling matches
  // the kebab-case clap value-enum names from the CLI.
  repeated string include_kinds = 5;
  // Substring path filters. A path matches `include_paths` if any
  // entry is a substring of the path; matches `exclude_paths` likewise.
  repeated string include_paths = 6;
  repeated string exclude_paths = 7;
  // Number of slots to return at the top. Default 20.
  uint32 top_n = 8;
  // If true, populate per-actor histograms on each spot.
  bool include_actors = 9;
}

message GetSemanticHotSpotsResponse {
  repeated SemanticHotSpot spots = 1;
  uint32 states_walked = 2;
  uint32 total_events = 3;
}

message SemanticHotSpot {
  // "file" or "function".
  string key_kind = 1;
  string path = 2;
  // Empty unless `key_kind == "function"`.
  string function = 3;
  uint32 event_count = 4;
  uint32 state_count = 5;
  string first_seen = 6;
  string last_seen = 7;
  // Histogram of event kinds (e.g. {"signature_changed": 3,
  // "function_extracted": 2}). Sums to `event_count`.
  map<string, uint32> by_kind = 8;
  // Histogram of actors (e.g. {"alice <a@x>": 4}). Empty unless the
  // request set `include_actors = true`.
  map<string, uint32> by_actor = 9;
}

message ActionSummary {
  string id = 1;
  optional string from_state = 2;
  string to_state = 3;
  string operation_json = 4;
  string description = 5;
  repeated string semantic_changes_json = 6;
  string timestamp = 7;
}

message ActionsResponse {
  repeated ActionSummary actions = 1;
}

message StatePrincipal {
  string name = 1;
  string email = 2;
}

message StateAgent {
  string provider = 1;
  string model = 2;
}

message StateVerification {
  uint32 tests_passed = 1;
  uint32 tests_failed = 2;
  optional float coverage_pct = 3;
  optional float coverage_delta = 4;
  uint32 lint_warnings = 5;
}

message StateSummary {
  bytes change_id = 1;
  repeated bytes parents = 2;
  string intent = 3;
  google.protobuf.Timestamp created_at = 4;
  StatePrincipal principal = 5;
  optional StateAgent agent = 6;
  optional float confidence = 7;
  StateStatus status = 8;
  optional StateVerification verification = 9;
  bytes logical_change_id = 10;
}

message ListStatesResponse {
  repeated StateSummary states = 1;
}

message BlameOrigin {
  bytes change_id = 1;
  string author = 2;
  string timestamp = 3;
  string principal_name = 4;
  string principal_email = 5;
  string agent_provider = 6;
  string agent_model = 7;
}

message BlameLine {
  uint32 line_number = 1;
  string content = 2;
  bytes change_id = 3;
  string author = 4;
  string timestamp = 5;
  repeated BlameOrigin origins = 6;
  bool carried = 7;
}

message BlameSummary {
  uint32 total_lines = 1;
  uint32 distinct_origin_count = 2;
  uint32 multi_origin_line_count = 3;
  uint32 carried_line_count = 4;
}

message GetBlameResponse {
  string path = 1;
  bytes state_id = 2;
  bool has_provenance = 3;
  repeated BlameLine lines = 4;
  BlameSummary summary = 5;
}

enum ProvenanceFileStatus {
  PROVENANCE_FILE_STATUS_UNSPECIFIED = 0;
  PROVENANCE_FILE_STATUS_AVAILABLE = 1;
  PROVENANCE_FILE_STATUS_PROVENANCE_ABSENT = 2;
  PROVENANCE_FILE_STATUS_BINARY = 3;
  PROVENANCE_FILE_STATUS_FILE_NOT_FOUND = 4;
  PROVENANCE_FILE_STATUS_ERROR = 5;
  PROVENANCE_FILE_STATUS_PENDING = 6;
}

enum DerivedSummaryBatchStatus {
  DERIVED_SUMMARY_BATCH_STATUS_UNSPECIFIED = 0;
  DERIVED_SUMMARY_BATCH_STATUS_READY = 1;
  DERIVED_SUMMARY_BATCH_STATUS_PENDING = 2;
  DERIVED_SUMMARY_BATCH_STATUS_RUNNING = 3;
  DERIVED_SUMMARY_BATCH_STATUS_FAILED = 4;
}

message ProvenanceFileSummary {
  string path = 1;
  ProvenanceFileStatus status = 2;
  optional BlameSummary summary = 3;
  uint32 authored_line_count = 4;
}

message ListProvenanceSummariesResponse {
  bytes state_id = 1;
  repeated ProvenanceFileSummary files = 2;
  string job_id = 3;
  DerivedSummaryBatchStatus batch_status = 4;
  string batch_label = 5;
}

// ── Context / Annotation messages ────────────────────────

enum StalenessStatus {
  STALENESS_UNKNOWN = 0;
  STALENESS_FRESH = 1;
  STALENESS_SOURCE_CHANGED = 2;
  STALENESS_SYMBOL_MISSING = 3;
}

message SymbolScope {
  string name = 1;
  optional uint32 resolved_start = 2;
  optional uint32 resolved_end = 3;
}

message LineRange {
  uint32 start = 1;
  uint32 end = 2;
}

message AnnotationScope {
  oneof scope {
    bool file = 1;
    SymbolScope symbol = 2;
    LineRange lines = 3;
  }
}

message ContextAnnotation {
  string id = 1;
  AnnotationScope scope = 2;
  string content = 3;
  repeated string tags = 4;
  string attribution = 5;
  google.protobuf.Timestamp created_at = 6;
  optional bytes source_hash = 7;
  optional string created_at_state = 8;
  StalenessStatus staleness = 9;
  ContextAnnotationStatus status = 10;
  ContextAnnotationKind kind = 11;
  uint32 revision_count = 12;
  optional string supersedes_annotation_id = 13;
  optional uint32 supersedes_rewrite_pct = 14;
}

message AnnotatedFile {
  string path = 1;
  repeated ContextAnnotation annotations = 2;
}

message StateContextEntry {
  bytes state_id = 1;
  repeated ContextAnnotation annotations = 2;
}

message ListContextRequest {
  string repo_path = 1;
  string ref = 2;
  optional string prefix = 3;
  optional string tag_filter = 4;
}

message ListContextResponse {
  repeated AnnotatedFile files = 1;
  ContextSummary summary = 2;
  repeated StateContextEntry states = 3;
}

message ContextRevision {
  string revision_id = 1;
  ContextAnnotationKind kind = 2;
  string content = 3;
  repeated string tags = 4;
  string attribution = 5;
  google.protobuf.Timestamp created_at = 6;
  optional bytes source_hash = 7;
  optional string created_at_state = 8;
}

message GetContextHistoryRequest {
  string repo_path = 1;
  string ref = 2;
  string annotation_id = 3;
}

message GetContextHistoryResponse {
  ContextAnnotation annotation = 1;
  repeated ContextRevision revisions = 2;
  optional string target_path = 3;
  optional bytes target_state_id = 4;
}

message SetContextRequest {
  string repo_path = 1;
  string path = 2;
  AnnotationScope scope = 3;
  repeated string tags = 4;
  string content = 5;
  string agent_provider = 6;
  string agent_model = 7;
  optional bytes target_state_id = 8;
  ContextAnnotationKind kind = 9;
  // Idempotency (audit-idempotency enforces tag 15).
  string client_operation_id = 15;
}

message SetContextResponse {
  // Newly-captured ChangeId carrying the annotation.
  bytes state = 1;
  uint32 annotations = 2;
}

message ReviseContextRequest {
  string repo_path = 1;
  string annotation_id = 2;
  string content = 3;
  repeated string tags = 4;
  string agent_provider = 5;
  string agent_model = 6;
  ContextAnnotationKind kind = 7;
}

message ReviseContextResponse {
  // Newly-captured ChangeId carrying the revision.
  bytes state = 1;
  uint32 revisions = 2;
}

message SupersedeContextRequest {
  string repo_path = 1;
  string annotation_id = 2;
  string path = 3;
  AnnotationScope scope = 4;
  repeated string tags = 5;
  string content = 6;
  string agent_provider = 7;
  string agent_model = 8;
  optional bytes target_state_id = 9;
  ContextAnnotationKind kind = 10;
}

message SupersedeContextResponse {
  // Newly-captured ChangeId carrying the supersession.
  bytes state = 1;
  string new_annotation_id = 2;
  uint32 rewrite_pct = 3;
}

message ContextSummary {
  uint32 total_annotations = 1;
  uint32 file_count = 2;
  uint32 stale_count = 3;
  uint32 invariant_count = 4;
  uint32 rationale_count = 5;
  uint32 constraint_count = 6;
  uint32 state_guidance_count = 7;
  uint32 superseded_count = 8;
  uint32 major_rewrite_count = 9;
}

enum ContextAnnotationStatus {
  CONTEXT_ANNOTATION_STATUS_UNSPECIFIED = 0;
  CONTEXT_ANNOTATION_STATUS_ACTIVE = 1;
  CONTEXT_ANNOTATION_STATUS_SUPERSEDED = 2;
}

enum ContextAnnotationKind {
  CONTEXT_ANNOTATION_KIND_UNSPECIFIED = 0;
  CONTEXT_ANNOTATION_KIND_CONSTRAINT = 1;
  CONTEXT_ANNOTATION_KIND_INVARIANT = 2;
  CONTEXT_ANNOTATION_KIND_RATIONALE = 3;
}

enum ContextSuggestionTier {
  CONTEXT_SUGGESTION_TIER_UNSPECIFIED = 0;
  CONTEXT_SUGGESTION_TIER_MEDIUM = 1;
  CONTEXT_SUGGESTION_TIER_HIGH = 2;
}

message ContextSuggestion {
  string path = 1;
  uint32 score = 2;
  ContextSuggestionTier tier = 3;
  repeated string reasons = 4;
  uint32 recent_changes = 5;
  uint32 distinct_states = 6;
  uint32 distinct_agents = 7;
  bool has_context = 8;
  uint32 stale_annotations = 9;
}

message ListContextSuggestionsRequest {
  string repo_path = 1;
  string ref = 2;
  uint32 limit = 3;
}

message ListContextSuggestionsResponse {
  repeated ContextSuggestion suggestions = 1;
}
