///|
/// evolve/github_client.mbt —— GitHub 搜索 API 客户端(L43,供 self_search 使用)
///
/// 原则:纯计算,零网络、零 token。HTTP 执行由调用方通过 `fetch` 注入;
/// 本模块只负责:构造请求 URL/参数 + 把 GitHub API JSON 响应解析为 self_search
/// 的 `SearchCandidate` 数组。这样 self_search 模块可以直接消费结果,
/// 走 distill_candidates 去重+可移植性过滤。

///|
/// GitHub 仓库搜索结果项
pub struct GhRepo {
  full_name : String // owner/repo
  description : String
  stargazers_count : Int32
  language : String
  html_url : String
  topics : Array[String]
}

///|
/// GitHub 代码搜索结果项
pub struct GhCode {
  repository : String // owner/repo
  path : String
  html_url : String
  score : Double
}

///|
/// 构造 GitHub 仓库搜索 API URL
///   query: 搜索关键词(如 "moonbit ltl parser")
///   per_page: 每页条数(1~100,默认 30)
///   sort: 排序字段("stars"/"updated"/"",空=默认 best match)
fn clamp_pp(n : Int32) -> Int32 {
  if n < 1 {
    1
  } else if n > 100 {
    100
  } else {
    n
  }
}

///|
/// 构造 GitHub 仓库搜索 API URL
///   query: 搜索关键词(如 "moonbit ltl parser")
///   per_page: 每页条数(1~100,默认 30)
///   sort: 排序字段("stars"/"updated"/"",空=默认 best match)
pub fn repo_search_url(
  query : String,
  per_page~ : Int32 = 30,
  sort~ : String = "",
) -> String {
  let q = query.trim().to_owned()
  let pp = clamp_pp(per_page)
  let sort_param = if sort.length() > 0 { "&sort=" + sort } else { "" }
  "https://api.github.com/search/repositories?q=" +
  q +
  "&per_page=" +
  pp.to_string() +
  sort_param
}

///|
/// 构造 GitHub 代码搜索 API URL
pub fn code_search_url(query : String, per_page~ : Int32 = 30) -> String {
  let q = query.trim().to_owned()
  let pp = clamp_pp(per_page)
  "https://api.github.com/search/code?q=" + q + "&per_page=" + pp.to_string()
}

///|
/// 构造 GitHub topic 搜索 API URL
pub fn topic_search_url(topic : String, per_page~ : Int32 = 30) -> String {
  let t = topic.trim().to_owned()
  let pp = clamp_pp(per_page)
  "https://api.github.com/search/repositories?q=topic:" +
  t +
  "&per_page=" +
  pp.to_string()
}

///|
/// 解析 GitHub 仓库搜索 API 的 JSON 响应为 GhRepo 数组
/// 容错:任何解析失败都跳过该项,不崩溃
pub fn parse_repo_search(json_text : String) -> Array[GhRepo] {
  let out : Array[GhRepo] = []
  let j = @json.parse(json_text) catch { _ => Json::null() }
  let items = match j {
    Object(m) =>
      match m.get("items") {
        Some(Array(a)) => a
        _ => []
      }
    _ => []
  }
  for item in items {
    match item {
      Object(im) => {
        let full_name = match im.get("full_name") {
          Some(String(s)) => s
          _ => ""
        }
        let description = match im.get("description") {
          Some(String(s)) => s
          _ => ""
        }
        let stars = match im.get("stargazers_count") {
          Some(Number(n, ..)) => n.to_int()
          _ => 0
        }
        let language = match im.get("language") {
          Some(String(s)) => s
          _ => ""
        }
        let html_url = match im.get("html_url") {
          Some(String(s)) => s
          _ => ""
        }
        let topics = match im.get("topics") {
          Some(Array(a)) =>
            a.filter_map(fn(t) {
              match t {
                String(s) => Some(s)
                _ => None
              }
            })
          _ => []
        }
        if full_name.length() > 0 {
          out.push({
            full_name,
            description,
            stargazers_count: stars,
            language,
            html_url,
            topics,
          })
        }
      }
      _ => ()
    }
  }
  out
}

///|
/// 解析 GitHub 代码搜索 API 的 JSON 响应为 GhCode 数组
pub fn parse_code_search(json_text : String) -> Array[GhCode] {
  let out : Array[GhCode] = []
  let j = @json.parse(json_text) catch { _ => Json::null() }
  let items = match j {
    Object(m) =>
      match m.get("items") {
        Some(Array(a)) => a
        _ => []
      }
    _ => []
  }
  for item in items {
    match item {
      Object(im) => {
        let repo = match im.get("repository") {
          Some(Object(rm)) =>
            match rm.get("full_name") {
              Some(String(s)) => s
              _ => ""
            }
          _ => ""
        }
        let path = match im.get("path") {
          Some(String(s)) => s
          _ => ""
        }
        let html_url = match im.get("html_url") {
          Some(String(s)) => s
          _ => ""
        }
        let score = match im.get("score") {
          Some(Number(n, ..)) => n
          _ => 0.0
        }
        if repo.length() > 0 {
          out.push({ repository: repo, path, html_url, score, })
        }
      }
      _ => ()
    }
  }
  out
}

///|
/// 把 GhRepo 数组转成 self_search 的 SearchCandidate 数组
///   - name = full_name
///   - title = full_name (description 可附在 mechanism)
///   - source = "gh"
///   - mechanism = description + language + topics(供可移植性评估)
pub fn repos_to_candidates(repos : Array[GhRepo]) -> Array[SearchCandidate] {
  repos.map(fn(r) {
    let mech = r.description +
      " | lang:" +
      r.language +
      " | topics:" +
      r.topics.join(",")
    analyze_candidate(r.full_name, r.full_name, "gh", mech)
  })
}

///|
/// 把 GhCode 数组转成 self_search 的 SearchCandidate 数组
///   - name = repository:path
///   - source = "gh-code"
///   - mechanism = path(供可移植性评估,含 "extract" 等词时可判可移植)
pub fn code_to_candidates(codes : Array[GhCode]) -> Array[SearchCandidate] {
  codes.map(fn(c) {
    let name = c.repository + ":" + c.path
    let mech = "extract " + c.path + " from " + c.repository
    analyze_candidate(name, name, "gh-code", mech)
  })
}

///|
/// 一站式:把仓库搜索 JSON 响应直接蒸馏为可移植候选
/// (parse → to_candidates → distill)
pub fn distill_repo_search(json_text : String) -> Array[SearchCandidate] {
  let repos = parse_repo_search(json_text)
  let cands = repos_to_candidates(repos)
  distill_candidates(cands)
}

///|
/// 一站式:把代码搜索 JSON 响应直接蒸馏为可移植候选
pub fn distill_code_search(json_text : String) -> Array[SearchCandidate] {
  let codes = parse_code_search(json_text)
  let cands = code_to_candidates(codes)
  distill_candidates(cands)
}

///|
/// 把 SearchCandidate 数组转成 TSV 行文本(name\ttitle\tsource\tmechanism),
/// 供 run_external_search 的行解析器消费。纯计算,不碰 IO。
///   - portable=false 的候选也输出(让下游 distill 决定过滤,不在这里硬编码门槛)
///   - 字段内含 Tab 替换为空格,防止破坏行解析
pub fn candidates_to_tsv_lines(cands : Array[SearchCandidate]) -> String {
  let lines : Array[String] = []
  for c in cands {
    let name = c.name.replace_all(old="\t", new=" ")
    let title = c.title.replace_all(old="\t", new=" ")
    let source = c.source.replace_all(old="\t", new=" ")
    let mech = c.mechanism.replace_all(old="\t", new=" ")
    lines.push(name + "\t" + title + "\t" + source + "\t" + mech)
  }
  lines.join("\n")
}

///|
/// 端到端:GitHub 仓库搜索 JSON → TSV 行(可直接喂 run_external_search 的行解析器)
pub fn repo_search_to_tsv(json_text : String) -> String {
  let repos = parse_repo_search(json_text)
  let cands = repos_to_candidates(repos)
  candidates_to_tsv_lines(cands)
}

///|
/// 端到端:GitHub 代码搜索 JSON → TSV 行
pub fn code_search_to_tsv(json_text : String) -> String {
  let codes = parse_code_search(json_text)
  let cands = code_to_candidates(codes)
  candidates_to_tsv_lines(cands)
}

///|
/// 把 run_external_search 的原始输出(curl 返回的 GitHub JSON 文本)
/// 直接转成 self_search 可消费的 TSV 行。
///   - 自动判断是仓库搜索还是代码搜索 JSON(看 items[0] 是否含 full_name 顶层字段)
///   - 解析失败或空 items → 返回空串(下游 distill 得到空数组,零回归)
pub fn github_json_to_tsv(json_text : String) -> String {
  let repos = parse_repo_search(json_text)
  if repos.length() > 0 {
    return repo_search_to_tsv(json_text)
  }
  let codes = parse_code_search(json_text)
  if codes.length() > 0 {
    return code_search_to_tsv(json_text)
  }
  ""
}

///|
/// 生成 curl 命令(供外部执行,不内置网络)
///   - token 留空则不带 Authorization header(匿名,限流 60/h)
///   - 不回显 token 本体
pub fn curl_repo_search(query : String, token~ : String = "") -> String {
  let url = repo_search_url(query)
  let auth = if token.length() > 0 {
    " -H \"Authorization: Bearer ***\""
  } else {
    ""
  }
  "curl -s -H \"Accept: application/vnd.github+json\"" +
  auth +
  " \"" +
  url +
  "\""
}