///| Git-native Pull Request and Issue system types

///|
/// State of a Pull Request
pub(all) enum PrState {
  Open
  Merged
  Closed
} derive(Eq, Debug)

///|
/// State of an Issue
pub(all) enum IssueState {
  Open
  Closed
} derive(Eq, Debug)

///|
/// Unified state of an Issue/PullRequest work item
pub(all) enum WorkItemState {
  Open
  Merged
  Closed
} derive(Eq, Debug)

///|
/// Work item kind
pub(all) enum WorkItemKind {
  Issue
  PullRequest
} derive(Eq, Debug)

///|
pub fn IssueState::to_string(self : IssueState) -> String {
  match self {
    Open => "open"
    Closed => "closed"
  }
}

///|
pub impl Show for IssueState with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
pub fn PrState::to_string(self : PrState) -> String {
  match self {
    Open => "open"
    Merged => "merged"
    Closed => "closed"
  }
}

///|
pub impl Show for PrState with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
pub fn WorkItemState::to_string(self : WorkItemState) -> String {
  match self {
    Open => "open"
    Merged => "merged"
    Closed => "closed"
  }
}

///|
pub impl Show for WorkItemState with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
pub fn WorkItemKind::to_string(self : WorkItemKind) -> String {
  match self {
    Issue => "issue"
    PullRequest => "pull-request"
  }
}

///|
pub impl Show for WorkItemKind with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
pub fn PrState::to_work_item_state(self : PrState) -> WorkItemState {
  match self {
    Open => WorkItemState::Open
    Merged => WorkItemState::Merged
    Closed => WorkItemState::Closed
  }
}

///|
pub fn IssueState::to_work_item_state(self : IssueState) -> WorkItemState {
  match self {
    Open => WorkItemState::Open
    Closed => WorkItemState::Closed
  }
}

///|
pub fn WorkItemState::to_pr_state(self : WorkItemState) -> PrState {
  match self {
    Open => PrState::Open
    Merged => PrState::Merged
    Closed => PrState::Closed
  }
}

///|
pub fn WorkItemState::to_issue_state(self : WorkItemState) -> IssueState? {
  match self {
    Open => Some(IssueState::Open)
    Closed => Some(IssueState::Closed)
    Merged => None
  }
}

///|
/// Pull Request data
pub struct PullRequest {
  id : String
  title : String
  body : String
  source_branch : String // refs/heads/feature-x
  source_repo : String? // owner/repo of source branch
  source_ref : String? // refs/heads/feature-x in source repo
  source_commit : @bit.ObjectId
  target_branch : String // refs/heads/main
  target_commit : @bit.ObjectId
  author : String
  created_at : Int64
  updated_at : Int64
  state : PrState
  labels : Array[String]
  closes_issues : Array[String] // Issues this PR closes
  merge_commit : @bit.ObjectId?
}

///|
pub fn PullRequest::new(
  id : String,
  title : String,
  body : String,
  source_branch : String,
  source_commit : @bit.ObjectId,
  target_branch : String,
  target_commit : @bit.ObjectId,
  author : String,
  created_at : Int64,
  updated_at : Int64,
  state : PrState,
  labels : Array[String],
  closes_issues? : Array[String] = [],
  merge_commit? : @bit.ObjectId? = None,
  source_repo? : String? = None,
  source_ref? : String? = None,
) -> PullRequest {
  {
    id,
    title,
    body,
    source_branch,
    source_repo,
    source_ref,
    source_commit,
    target_branch,
    target_commit,
    author,
    created_at,
    updated_at,
    state,
    labels,
    closes_issues,
    merge_commit,
  }
}

///|
pub fn PullRequest::id(self : PullRequest) -> String {
  self.id
}

///|
pub fn PullRequest::title(self : PullRequest) -> String {
  self.title
}

///|
pub fn PullRequest::body(self : PullRequest) -> String {
  self.body
}

///|
pub fn PullRequest::source_branch(self : PullRequest) -> String {
  self.source_branch
}

///|
pub fn PullRequest::source_repo(self : PullRequest) -> String? {
  self.source_repo
}

///|
pub fn PullRequest::source_ref(self : PullRequest) -> String? {
  self.source_ref
}

///|
pub fn PullRequest::source_commit(self : PullRequest) -> @bit.ObjectId {
  self.source_commit
}

///|
pub fn PullRequest::target_branch(self : PullRequest) -> String {
  self.target_branch
}

///|
pub fn PullRequest::target_commit(self : PullRequest) -> @bit.ObjectId {
  self.target_commit
}

///|
pub fn PullRequest::author(self : PullRequest) -> String {
  self.author
}

///|
pub fn PullRequest::created_at(self : PullRequest) -> Int64 {
  self.created_at
}

///|
pub fn PullRequest::updated_at(self : PullRequest) -> Int64 {
  self.updated_at
}

///|
pub fn PullRequest::state(self : PullRequest) -> PrState {
  self.state
}

///|
pub fn PullRequest::labels(self : PullRequest) -> Array[String] {
  self.labels
}

///|
pub fn PullRequest::closes_issues(self : PullRequest) -> Array[String] {
  self.closes_issues
}

///|
pub fn PullRequest::merge_commit(self : PullRequest) -> @bit.ObjectId? {
  self.merge_commit
}

///|
/// Patch capability of a work item
pub struct WorkItemPatch {
  source_branch : String
  source_repo : String?
  source_ref : String?
  source_commit : @bit.ObjectId
  target_branch : String
  target_commit : @bit.ObjectId
  closes_issues : Array[String]
  merge_commit : @bit.ObjectId?
}

///|
pub fn WorkItemPatch::new(
  source_branch : String,
  source_commit : @bit.ObjectId,
  target_branch : String,
  target_commit : @bit.ObjectId,
  closes_issues? : Array[String] = [],
  merge_commit? : @bit.ObjectId? = None,
  source_repo? : String? = None,
  source_ref? : String? = None,
) -> WorkItemPatch {
  {
    source_branch,
    source_repo,
    source_ref,
    source_commit,
    target_branch,
    target_commit,
    closes_issues,
    merge_commit,
  }
}

///|
pub fn WorkItemPatch::source_branch(self : WorkItemPatch) -> String {
  self.source_branch
}

///|
pub fn WorkItemPatch::source_repo(self : WorkItemPatch) -> String? {
  self.source_repo
}

///|
pub fn WorkItemPatch::source_ref(self : WorkItemPatch) -> String? {
  self.source_ref
}

///|
pub fn WorkItemPatch::source_commit(self : WorkItemPatch) -> @bit.ObjectId {
  self.source_commit
}

///|
pub fn WorkItemPatch::target_branch(self : WorkItemPatch) -> String {
  self.target_branch
}

///|
pub fn WorkItemPatch::target_commit(self : WorkItemPatch) -> @bit.ObjectId {
  self.target_commit
}

///|
pub fn WorkItemPatch::closes_issues(self : WorkItemPatch) -> Array[String] {
  self.closes_issues
}

///|
pub fn WorkItemPatch::merge_commit(self : WorkItemPatch) -> @bit.ObjectId? {
  self.merge_commit
}

///|
/// Unified work item view of issue and pull request
pub struct WorkItem {
  id : String
  title : String
  body : String
  author : String
  created_at : Int64
  updated_at : Int64
  state : WorkItemState
  labels : Array[String]
  assignees : Array[String]
  linked_prs : Array[String]
  linked_issues : Array[String]
  patch : WorkItemPatch?
  parent_id : String?
  blocked_by : Array[String]
  blocking : Array[String]
}

///|
pub fn WorkItem::new(
  id : String,
  title : String,
  body : String,
  author : String,
  created_at : Int64,
  updated_at : Int64,
  state : WorkItemState,
  labels? : Array[String] = [],
  assignees? : Array[String] = [],
  linked_prs? : Array[String] = [],
  linked_issues? : Array[String] = [],
  patch? : WorkItemPatch? = None,
  parent_id? : String? = None,
  blocked_by? : Array[String] = [],
  blocking? : Array[String] = [],
) -> WorkItem {
  {
    id,
    title,
    body,
    author,
    created_at,
    updated_at,
    state,
    labels,
    assignees,
    linked_prs,
    linked_issues,
    patch,
    parent_id,
    blocked_by,
    blocking,
  }
}

///|
pub fn WorkItem::id(self : WorkItem) -> String {
  self.id
}

///|
pub fn WorkItem::title(self : WorkItem) -> String {
  self.title
}

///|
pub fn WorkItem::body(self : WorkItem) -> String {
  self.body
}

///|
pub fn WorkItem::author(self : WorkItem) -> String {
  self.author
}

///|
pub fn WorkItem::created_at(self : WorkItem) -> Int64 {
  self.created_at
}

///|
pub fn WorkItem::updated_at(self : WorkItem) -> Int64 {
  self.updated_at
}

///|
pub fn WorkItem::state(self : WorkItem) -> WorkItemState {
  self.state
}

///|
pub fn WorkItem::kind(self : WorkItem) -> WorkItemKind {
  match self.patch {
    Some(_) => WorkItemKind::PullRequest
    None => WorkItemKind::Issue
  }
}

///|
pub fn WorkItem::labels(self : WorkItem) -> Array[String] {
  self.labels
}

///|
pub fn WorkItem::assignees(self : WorkItem) -> Array[String] {
  self.assignees
}

///|
pub fn WorkItem::linked_prs(self : WorkItem) -> Array[String] {
  self.linked_prs
}

///|
pub fn WorkItem::linked_issues(self : WorkItem) -> Array[String] {
  self.linked_issues
}

///|
pub fn WorkItem::blocked_by(self : WorkItem) -> Array[String] {
  self.blocked_by
}

///|
pub fn WorkItem::blocking(self : WorkItem) -> Array[String] {
  self.blocking
}

///|
pub fn WorkItem::patch(self : WorkItem) -> WorkItemPatch? {
  self.patch
}

///|
pub fn WorkItem::to_pull_request(self : WorkItem) -> PullRequest? {
  match self.patch {
    None => None
    Some(patch) =>
      Some(
        PullRequest::new(
          self.id,
          self.title,
          self.body,
          patch.source_branch,
          patch.source_commit,
          patch.target_branch,
          patch.target_commit,
          self.author,
          self.created_at,
          self.updated_at,
          self.state.to_pr_state(),
          self.labels,
          closes_issues=patch.closes_issues,
          merge_commit=patch.merge_commit,
          source_repo=patch.source_repo,
          source_ref=patch.source_ref,
        ),
      )
  }
}

///|
pub fn WorkItem::to_issue(self : WorkItem) -> Issue? {
  match self.patch {
    Some(_) => None
    None =>
      match self.state.to_issue_state() {
        None => None
        Some(issue_state) =>
          Some(
            Issue::new(
              self.id,
              self.title,
              self.body,
              self.author,
              self.created_at,
              self.updated_at,
              issue_state,
              labels=self.labels,
              assignees=self.assignees,
              linked_prs=self.linked_prs,
              linked_issues=self.linked_issues,
              parent_id=self.parent_id,
              blocked_by=self.blocked_by,
              blocking=self.blocking,
            ),
          )
      }
  }
}

///|
pub fn PullRequest::to_work_item(self : PullRequest) -> WorkItem {
  let patch = WorkItemPatch::new(
    self.source_branch,
    self.source_commit,
    self.target_branch,
    self.target_commit,
    closes_issues=self.closes_issues,
    merge_commit=self.merge_commit,
    source_repo=self.source_repo,
    source_ref=self.source_ref,
  )
  WorkItem::new(
    self.id,
    self.title,
    self.body,
    self.author,
    self.created_at,
    self.updated_at,
    self.state.to_work_item_state(),
    labels=self.labels,
    patch=Some(patch),
  )
}

///|
/// Comment on a Pull Request
pub struct PrComment {
  id : String
  pr_id : String
  author : String
  body : String
  created_at : Int64
  reply_to : String? // threading
  file_path : String? // file-level comment
  line_number : Int? // line-level comment
  commit_id : @bit.ObjectId?
}

///|
pub fn PrComment::new(
  id : String,
  pr_id : String,
  author : String,
  body : String,
  created_at : Int64,
  reply_to? : String? = None,
  file_path? : String? = None,
  line_number? : Int? = None,
  commit_id? : @bit.ObjectId? = None,
) -> PrComment {
  {
    id,
    pr_id,
    author,
    body,
    created_at,
    reply_to,
    file_path,
    line_number,
    commit_id,
  }
}

///|
pub fn PrComment::id(self : PrComment) -> String {
  self.id
}

///|
pub fn PrComment::pr_id(self : PrComment) -> String {
  self.pr_id
}

///|
pub fn PrComment::author(self : PrComment) -> String {
  self.author
}

///|
pub fn PrComment::body(self : PrComment) -> String {
  self.body
}

///|
pub fn PrComment::created_at(self : PrComment) -> Int64 {
  self.created_at
}

///|
pub fn PrComment::reply_to(self : PrComment) -> String? {
  self.reply_to
}

///|
pub fn PrComment::file_path(self : PrComment) -> String? {
  self.file_path
}

///|
pub fn PrComment::line_number(self : PrComment) -> Int? {
  self.line_number
}

///|
pub fn PrComment::commit_id(self : PrComment) -> @bit.ObjectId? {
  self.commit_id
}

///|
/// Review verdict
pub(all) enum ReviewVerdict {
  Approved
  RequestChanges
  Comment
} derive(Eq, Debug)

///|
pub fn ReviewVerdict::to_string(self : ReviewVerdict) -> String {
  match self {
    Approved => "approved"
    RequestChanges => "request-changes"
    Comment => "comment"
  }
}

///|
pub impl Show for ReviewVerdict with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
/// Review on a Pull Request
pub struct PrReview {
  id : String
  pr_id : String
  author : String
  verdict : ReviewVerdict
  body : String
  created_at : Int64
  commit_id : @bit.ObjectId
}

///|
pub fn PrReview::new(
  id : String,
  pr_id : String,
  author : String,
  verdict : ReviewVerdict,
  body : String,
  created_at : Int64,
  commit_id : @bit.ObjectId,
) -> PrReview {
  { id, pr_id, author, verdict, body, created_at, commit_id }
}

///|
pub fn PrReview::id(self : PrReview) -> String {
  self.id
}

///|
pub fn PrReview::pr_id(self : PrReview) -> String {
  self.pr_id
}

///|
pub fn PrReview::author(self : PrReview) -> String {
  self.author
}

///|
pub fn PrReview::verdict(self : PrReview) -> ReviewVerdict {
  self.verdict
}

///|
pub fn PrReview::body(self : PrReview) -> String {
  self.body
}

///|
pub fn PrReview::created_at(self : PrReview) -> Int64 {
  self.created_at
}

///|
pub fn PrReview::commit_id(self : PrReview) -> @bit.ObjectId {
  self.commit_id
}

///|
/// PR operation error
pub(all) suberror PrError {
  NotFound(String)
  InvalidFormat(String)
  InvalidState(String)
  MergeConflict(Array[String])
} derive(Debug, Eq)

///|
/// Issue data
pub struct Issue {
  id : String
  title : String
  body : String
  author : String
  created_at : Int64
  updated_at : Int64
  state : IssueState
  labels : Array[String]
  assignees : Array[String]
  linked_prs : Array[String] // Related PRs
  linked_issues : Array[String] // Cross-repo issue references
  parent_id : String?
  blocked_by : Array[String] // Issues that block this one
  blocking : Array[String] // Issues this one blocks
}

///|
pub fn Issue::new(
  id : String,
  title : String,
  body : String,
  author : String,
  created_at : Int64,
  updated_at : Int64,
  state : IssueState,
  labels? : Array[String] = [],
  assignees? : Array[String] = [],
  linked_prs? : Array[String] = [],
  linked_issues? : Array[String] = [],
  parent_id? : String? = None,
  blocked_by? : Array[String] = [],
  blocking? : Array[String] = [],
) -> Issue {
  {
    id,
    title,
    body,
    author,
    created_at,
    updated_at,
    state,
    labels,
    assignees,
    linked_prs,
    linked_issues,
    parent_id,
    blocked_by,
    blocking,
  }
}

///|
pub fn Issue::id(self : Issue) -> String {
  self.id
}

///|
pub fn Issue::title(self : Issue) -> String {
  self.title
}

///|
pub fn Issue::body(self : Issue) -> String {
  self.body
}

///|
pub fn Issue::author(self : Issue) -> String {
  self.author
}

///|
pub fn Issue::created_at(self : Issue) -> Int64 {
  self.created_at
}

///|
pub fn Issue::updated_at(self : Issue) -> Int64 {
  self.updated_at
}

///|
pub fn Issue::state(self : Issue) -> IssueState {
  self.state
}

///|
pub fn Issue::labels(self : Issue) -> Array[String] {
  self.labels
}

///|
pub fn Issue::assignees(self : Issue) -> Array[String] {
  self.assignees
}

///|
pub fn Issue::linked_prs(self : Issue) -> Array[String] {
  self.linked_prs
}

///|
pub fn Issue::linked_issues(self : Issue) -> Array[String] {
  self.linked_issues
}

///|
pub fn Issue::blocked_by(self : Issue) -> Array[String] {
  self.blocked_by
}

///|
pub fn Issue::blocking(self : Issue) -> Array[String] {
  self.blocking
}

///|
pub fn Issue::to_work_item(self : Issue) -> WorkItem {
  WorkItem::new(
    self.id,
    self.title,
    self.body,
    self.author,
    self.created_at,
    self.updated_at,
    self.state.to_work_item_state(),
    labels=self.labels,
    assignees=self.assignees,
    linked_prs=self.linked_prs,
    linked_issues=self.linked_issues,
    parent_id=self.parent_id,
    blocked_by=self.blocked_by,
    blocking=self.blocking,
  )
}

///|
/// Comment on an Issue
pub struct IssueComment {
  id : String
  issue_id : String
  author : String
  body : String
  created_at : Int64
  reply_to : String? // threading
}

///|
pub fn IssueComment::new(
  id : String,
  issue_id : String,
  author : String,
  body : String,
  created_at : Int64,
  reply_to? : String? = None,
) -> IssueComment {
  { id, issue_id, author, body, created_at, reply_to }
}

///|
pub fn IssueComment::id(self : IssueComment) -> String {
  self.id
}

///|
pub fn IssueComment::issue_id(self : IssueComment) -> String {
  self.issue_id
}

///|
pub fn IssueComment::author(self : IssueComment) -> String {
  self.author
}

///|
pub fn IssueComment::body(self : IssueComment) -> String {
  self.body
}

///|
pub fn IssueComment::created_at(self : IssueComment) -> Int64 {
  self.created_at
}

///|
pub fn IssueComment::reply_to(self : IssueComment) -> String? {
  self.reply_to
}

///|
/// Note attached to a commit (Git notes compatible)
pub struct Note {
  commit_id : @bit.ObjectId // The commit this note is attached to
  body : String
  author : String
  created_at : Int64
  ns : String // e.g., "commits", "review", "tips"
}

///|
pub fn Note::new(
  commit_id : @bit.ObjectId,
  body : String,
  author : String,
  created_at : Int64,
  ns? : String = "commits",
) -> Note {
  { commit_id, body, author, created_at, ns }
}

///|
pub fn Note::commit_id(self : Note) -> @bit.ObjectId {
  self.commit_id
}

///|
pub fn Note::body(self : Note) -> String {
  self.body
}

///|
pub fn Note::author(self : Note) -> String {
  self.author
}

///|
pub fn Note::created_at(self : Note) -> Int64 {
  self.created_at
}

///|
pub fn Note::ns(self : Note) -> String {
  self.ns
}