///| Hub - Git-native hub system (Pull Requests, Issues)

///|
/// Hub manages Pull Requests and Issues stored in git notes
pub struct Hub {
  mut store : HubStore
}

///|
/// Load an existing Hub from git notes
pub fn Hub::load(
  objects : &@lib.ObjectStore,
  refs : &@lib.RefStore,
  node_id? : String = "local",
  signing_key? : String? = None,
  require_signed? : Bool = false,
) -> Hub raise @bit.GitError {
  let store = HubStore::load(
    objects,
    refs,
    node_id~,
    signing_key~,
    require_signed~,
  )
  { store, }
}

///|
/// Reload store from current object/ref state
pub fn Hub::reload_store(
  self : Hub,
  objects : &@lib.ObjectStore,
  refs : &@lib.RefStore,
) -> Unit raise @bit.GitError {
  self.store = HubStore::load(
    objects,
    refs,
    node_id=self.store.node_id(),
    signing_key=self.store.signing_key(),
    require_signed=self.store.require_signed(),
  )
}

///|
/// Initialize a new Hub
pub fn Hub::init(
  objects : &@lib.ObjectStore,
  refs : &@lib.RefStore,
  node_id? : String = "local",
  signing_key? : String? = None,
  require_signed? : Bool = false,
) -> Hub raise @bit.GitError {
  Hub::load(objects, refs, node_id~, signing_key~, require_signed~)
}

///|
/// Create a new Pull Request
pub fn Hub::create_pr(
  self : Hub,
  objects : &@lib.ObjectStore,
  refs : &@lib.RefStore,
  clock : &@lib.Clock,
  title : String,
  body : String,
  source_branch : String,
  target_branch : String,
  author : String,
  source_repo? : String? = None,
  source_ref? : String? = None,
) -> PullRequest raise @bit.GitError {
  let timestamp = clock.now()
  // Resolve source and target commits
  let source_commit = refs.resolve(source_branch) catch {
    _ =>
      raise @bit.GitError::InvalidObject(
        "Cannot resolve source branch: \{source_branch}",
      )
  }
  guard source_commit is Some(src_id) else {
    raise @bit.GitError::InvalidObject(
      "Source branch not found: \{source_branch}",
    )
  }
  let target_commit = refs.resolve(target_branch) catch {
    _ =>
      raise @bit.GitError::InvalidObject(
        "Cannot resolve target branch: \{target_branch}",
      )
  }
  guard target_commit is Some(tgt_id) else {
    raise @bit.GitError::InvalidObject(
      "Target branch not found: \{target_branch}",
    )
  }
  // Generate PR ID (stable, content-derived)
  let pr_id = generate_entity_id(
    "pr",
    author,
    timestamp,
    source_branch + "\n" + target_branch + "\n" + title + "\n" + body,
  )
  // Create PullRequest object
  let pr = PullRequest::new(
    pr_id,
    title,
    body,
    source_branch,
    src_id,
    target_branch,
    tgt_id,
    author,
    timestamp,
    timestamp,
    PrState::Open,
    [],
    source_repo~,
    source_ref~,
  )
  ignore(
    self.store.put_record(
      objects,
      refs,
      clock,
      work_item_meta_key(pr_id),
      canonical_work_item_record_kind(),
      pr.to_work_item().serialize(),
      author,
    ),
  )
  pr
}

///|
/// Get a Pull Request by ID
pub fn Hub::get_pr(
  self : Hub,
  objects : &@lib.ObjectStore,
  pr_id : String,
) -> PullRequest? {
  let work_item = self.get_work_item(objects, pr_id)
  match work_item {
    None => None
    Some(item) => item.to_pull_request()
  }
}

///|
/// List Pull Requests, optionally filtered by state
pub fn Hub::list_prs(
  self : Hub,
  objects : &@lib.ObjectStore,
  state? : PrState? = None,
) -> Array[PullRequest] {
  let item_state = match state {
    None => None
    Some(s) => Some(s.to_work_item_state())
  }
  let items = self.list_work_items(
    objects,
    state=item_state,
    kind=Some(WorkItemKind::PullRequest),
  )
  let result : Array[PullRequest] = []
  for item in items {
    match item.to_pull_request() {
      Some(pr) => result.push(pr)
      None => ()
    }
  }
  result
}

///|
/// Update a Pull Request (title, body, labels)
pub fn Hub::update_pr(
  self : Hub,
  objects : &@lib.ObjectStore,
  refs : &@lib.RefStore,
  clock : &@lib.Clock,
  pr_id : String,
  title? : String? = None,
  body? : String? = None,
  labels? : Array[String]? = None,
) -> PullRequest raise @bit.GitError {
  let pr = self.get_pr(objects, pr_id)
  guard pr is Some(existing) else {
    raise @bit.GitError::InvalidObject("PR not found: \{pr_id}")
  }
  let new_title = match title {
    Some(t) => t
    None => existing.title
  }
  let new_body = match body {
    Some(b) => b
    None => existing.body
  }
  let new_labels = match labels {
    Some(l) => l
    None => existing.labels
  }
  let updated = PullRequest::new(
    existing.id,
    new_title,
    new_body,
    existing.source_branch,
    existing.source_commit,
    existing.target_branch,
    existing.target_commit,
    existing.author,
    existing.created_at,
    clock.now(),
    existing.state,
    new_labels,
    source_repo=existing.source_repo,
    source_ref=existing.source_ref,
  )
  ignore(
    self.store.put_record(
      objects,
      refs,
      clock,
      work_item_meta_key(pr_id),
      canonical_work_item_record_kind(),
      updated.to_work_item().serialize(),
      existing.author,
    ),
  )
  updated
}

///|
/// Close a Pull Request
pub fn Hub::close_pr(
  self : Hub,
  objects : &@lib.ObjectStore,
  refs : &@lib.RefStore,
  clock : &@lib.Clock,
  pr_id : String,
) -> Unit raise @bit.GitError {
  let pr = self.get_pr(objects, pr_id)
  guard pr is Some(existing) else {
    raise @bit.GitError::InvalidObject("PR not found: \{pr_id}")
  }
  if existing.state != PrState::Open {
    raise @bit.GitError::InvalidObject("PR is not open: \{pr_id}")
  }
  // Create updated PR with Closed state
  let updated = PullRequest::new(
    existing.id,
    existing.title,
    existing.body,
    existing.source_branch,
    existing.source_commit,
    existing.target_branch,
    existing.target_commit,
    existing.author,
    existing.created_at,
    clock.now(),
    PrState::Closed,
    existing.labels,
    source_repo=existing.source_repo,
    source_ref=existing.source_ref,
  )
  ignore(
    self.store.put_record(
      objects,
      refs,
      clock,
      work_item_meta_key(pr_id),
      canonical_work_item_record_kind(),
      updated.to_work_item().serialize(),
      existing.author,
    ),
  )
}

///|
/// Reopen a closed Pull Request
pub fn Hub::reopen_pr(
  self : Hub,
  objects : &@lib.ObjectStore,
  refs : &@lib.RefStore,
  clock : &@lib.Clock,
  pr_id : String,
) -> Unit raise @bit.GitError {
  let pr = self.get_pr(objects, pr_id)
  guard pr is Some(existing) else {
    raise @bit.GitError::InvalidObject("PR not found: \{pr_id}")
  }
  if existing.state != PrState::Closed {
    raise @bit.GitError::InvalidObject("PR is not closed: \{pr_id}")
  }
  let updated = PullRequest::new(
    existing.id,
    existing.title,
    existing.body,
    existing.source_branch,
    existing.source_commit,
    existing.target_branch,
    existing.target_commit,
    existing.author,
    existing.created_at,
    clock.now(),
    PrState::Open,
    existing.labels,
    source_repo=existing.source_repo,
    source_ref=existing.source_ref,
  )
  ignore(
    self.store.put_record(
      objects,
      refs,
      clock,
      work_item_meta_key(pr_id),
      canonical_work_item_record_kind(),
      updated.to_work_item().serialize(),
      existing.author,
    ),
  )
}

///|
fn generate_entity_id(
  kind : String,
  author : String,
  timestamp : Int64,
  seed : String,
) -> String {
  let base = kind + "\n" + author + "\n" + timestamp.to_string() + "\n" + seed
  let (id, _compressed) = @bit.create_blob_string(base)
  short_hex_id(id.to_hex(), 8)
}

///|
fn short_hex_id(hex : String, n : Int) -> String {
  if hex.length() <= n {
    hex
  } else {
    String::unsafe_substring(hex, start=0, end=n)
  }
}