///|
// Golden replay tests: read contract/goldens/skills/*.json and verify
// that our MoonBit implementation produces equivalent results.

fn ffi_read_skills_golden(name : String) -> String {
  let dir = (@env.get_env_var("MNEMO_CONTRACT_DIR")).unwrap_or("./contract")
  let path = dir + "/goldens/skills/" + name + ".json"
  (try? @fs.read_file_to_string(path)).unwrap_or("")
}

///| Skills fixture dir relative to cwd (matches prior JS behavior).
fn ffi_skills_fixtures_path_golden() -> String {
  "./proto/fixtures/skills"
}

///| Load and parse a skills golden JSON file
fn sg_load_golden(name : String) -> Json {
  let raw = ffi_read_skills_golden(name)
  try {
    @json.parse(raw)
  } catch {
    _ => Json::null()
  }
}

///| Extract a string field from a Json object, or return default
fn sg_get_str(j : Json, key : String) -> String {
  match j {
    Json::Object(obj) =>
      match obj.get(key) {
        Some(Json::String(s)) => s
        _ => ""
      }
    _ => ""
  }
}

///| Extract an int field from a Json object, or return default
fn sg_get_int(j : Json, key : String) -> Int {
  match j {
    Json::Object(obj) =>
      match obj.get(key) {
        Some(Json::Number(n, ..)) => n.to_int()
        _ => 0
      }
    _ => 0
  }
}

///| Extract a bool field from a Json object, or return false
fn sg_get_bool(j : Json, key : String) -> Bool {
  match j {
    Json::Object(obj) =>
      match obj.get(key) {
        Some(Json::True) => true
        _ => false
      }
    _ => false
  }
}

///| Get the "expected" sub-object from a golden JSON
fn sg_expected(j : Json) -> Json {
  match j {
    Json::Object(obj) =>
      match obj.get("expected") {
        Some(e) => e
        _ => Json::null()
      }
    _ => Json::null()
  }
}

///| Get the "input" sub-object from a golden JSON
fn sg_input(j : Json) -> Json {
  match j {
    Json::Object(obj) =>
      match obj.get("input") {
        Some(i) => i
        _ => Json::null()
      }
    _ => Json::null()
  }
}

///| Find skill by name in array (manual loop — Iter has no find)
fn sg_find_by_name(skills : Array[SkillEntry], name : String) -> Bool {
  let mut found = false
  for s in skills {
    if s.name == name {
      found = true
    }
  }
  found
}

///| Golden 01: frontmatter parse
test "golden: 01_frontmatter_parse" {
  let j = sg_load_golden("01_frontmatter_parse")
  let input = sg_input(j)
  let expected = sg_expected(j)
  let content = sg_get_str(input, "content")
  let fm_json = match expected {
    Json::Object(obj) =>
      match obj.get("frontmatter") {
        Some(fm) => fm
        _ => Json::null()
      }
    _ => Json::null()
  }
  let expected_name = sg_get_str(fm_json, "name")
  let fm = parse_frontmatter(content)
  assert_eq(fm.name, Some(expected_name))
}

///| Golden 02: load_dir — count and names
test "golden: 02_load_dir" {
  let j = sg_load_golden("02_load_dir")
  let expected = sg_expected(j)
  let expected_count = sg_get_int(expected, "count")
  let dir = ffi_skills_fixtures_path_golden()
  let store = skills_store_load(dir, [])
  let skills = skills_store_list(store, None, None)
  assert_eq(skills.length(), expected_count)
}

///| Golden 03: filter_by_tools
test "golden: 03_filter_by_tools" {
  let j = sg_load_golden("03_filter_by_tools")
  let expected = sg_expected(j)
  let docker_hidden = sg_get_bool(expected, "docker_build_hidden_when_tool_available")
  let docker_shown = sg_get_bool(expected, "docker_build_shown_when_no_tool")
  let dir = ffi_skills_fixtures_path_golden()
  let store = skills_store_load(dir, [])
  let with_tool = skills_store_list(store, Some(["docker_build_tool"]), None)
  let without_tool = skills_store_list(store, Some([]), None)
  // Iter has no find — use manual loop
  let actual_hidden = !sg_find_by_name(with_tool, "docker-build")
  let actual_shown = sg_find_by_name(without_tool, "docker-build")
  assert_eq(actual_hidden, docker_hidden)
  assert_eq(actual_shown, docker_shown)
}

///| Golden 04: filter_by_platform
test "golden: 04_filter_by_platform" {
  let j = sg_load_golden("04_filter_by_platform")
  let expected = sg_expected(j)
  let deploy_on_linux = sg_get_bool(expected, "deploy_k8s_on_linux")
  let deploy_on_mac = sg_get_bool(expected, "deploy_k8s_on_mac")
  let dir = ffi_skills_fixtures_path_golden()
  let store = skills_store_load(dir, [])
  let for_linux = skills_store_list(store, Some(["kubectl"]), Some("linux"))
  let for_mac = skills_store_list(store, None, Some("mac"))
  let actual_linux = sg_find_by_name(for_linux, "deploy-k8s")
  let actual_mac = sg_find_by_name(for_mac, "deploy-k8s")
  assert_eq(actual_linux, deploy_on_linux)
  assert_eq(actual_mac, deploy_on_mac)
}

///| Golden 05: build_prompt
test "golden: 05_build_prompt" {
  let j = sg_load_golden("05_build_prompt")
  let expected = sg_expected(j)
  let contains_tdd = sg_get_bool(expected, "contains_tdd_workflow")
  let dir = ffi_skills_fixtures_path_golden()
  let store = skills_store_load(dir, [])
  let prompt = skills_store_build_prompt(store, None, None)
  assert_eq(prompt.contains("tdd-workflow"), contains_tdd)
  assert_eq(prompt.has_prefix("## Skills"), true)
}

///| Golden 06: skill_view
test "golden: 06_skill_view" {
  let j = sg_load_golden("06_skill_view")
  let expected = sg_expected(j)
  let expected_ok = sg_get_bool(expected, "ok")
  let expected_content_contains = sg_get_bool(expected, "content_contains")
  let dir = ffi_skills_fixtures_path_golden()
  let store = skills_store_load(dir, [])
  let result = skills_store_view(store, "tdd-workflow")
  match result {
    ViewOk(_name, content) => {
      assert_eq(true, expected_ok)
      assert_eq(content.contains("Red"), expected_content_contains)
    }
    ViewErr(_) => assert_eq(false, expected_ok)
  }
}

///| Golden 07: disabled filter — MoonBit matches proto behavior
test "golden: 07_disabled_filter — disabled skill excluded" {
  let j = sg_load_golden("07_disabled_filter")
  let expected = sg_expected(j)
  let supported = sg_get_bool(expected, "supported")
  assert_eq(supported, true)
  let dir = ffi_skills_fixtures_path_golden()
  let store = skills_store_load(dir, [])
  let all = skills_store_list(store, None, None)
  let filtered = skills_store_list(store, None, None, disabled=["tdd-workflow"])
  // Golden should record (all count - 1 == filtered count)
  assert_eq(filtered.length(), all.length() - 1)
  // tdd-workflow must be gone
  match find_skill_by_name(filtered, "tdd-workflow") {
    Some(_) => fail("tdd-workflow should have been disabled")
    None => ()
  }
  // Compare counts against golden's filtered_names array for cross-impl parity
  match expected {
    Json::Object(obj) =>
      match obj.get("filtered_names") {
        Some(Json::Array(names)) => assert_eq(filtered.length(), names.length())
        _ => ()
      }
    _ => ()
  }
}

///| Golden 08: metadata.hermes.tags — MoonBit now parses tags
test "golden: 08_metadata_tags — tags parsed from frontmatter" {
  let j = sg_load_golden("08_metadata_tags")
  let expected = sg_expected(j)
  let tags_count = sg_get_int(expected, "tags_count")
  assert_eq(tags_count, 2)
  // Live: parse the same content the proto golden used and verify MoonBit matches.
  let content = "---\nname: tagged-skill\ndescription: A skill with tags\nmetadata:\n  hermes:\n    tags: [alpha, beta]\n---\n\n# Tagged Skill\nBody.\n"
  let fm = parse_frontmatter(content)
  assert_eq(fm.tags.length(), tags_count)
  assert_eq(fm.tags.contains("alpha"), true)
  assert_eq(fm.tags.contains("beta"), true)
}

// ── FFI helpers for golden 10 temp dir (cross-target) ──────────────────────

///| Monotonic counter for unique tmp dirs across same-ms calls.
let _sk_tmp_counter : Array[Int] = [0]

fn sg_mkdtemp(prefix : String) -> String {
  let tmp = (@env.get_env_var("TMPDIR")).unwrap_or("/tmp")
  _sk_tmp_counter[0] = _sk_tmp_counter[0] + 1
  let suffix = @env.now().to_string() + "-" + _sk_tmp_counter[0].to_string()
  let dir = tmp + "/" + prefix + suffix
  let _ = try? @fs.create_dir(dir)

  dir
}

///| Recursive mkdir.
fn sg_mkdir(dir : String) -> Unit {
  if @fs.path_exists(dir) {
    return
  }
  let view = dir.view()
  let mut last = -1
  let mut i = 0
  while i < view.length() {
    if view.get_char(i) is Some('/') {
      last = i
    }
    i = i + 1
  }
  if last > 0 {
    sg_mkdir(view[0:last].to_string())
  }
  let _ = try? @fs.create_dir(dir)

}

fn sg_write_file(path : String, content : String) -> Unit {
  let _ = try? @fs.write_string_to_file(path, content)

}

fn sg_rmrf(dir : String) -> Unit {
  let _ = try? @fs.remove_dir(dir)

}

fn sg_path_join(a : String, b : String) -> String {
  if a.is_empty() {
    b
  } else if a.has_suffix("/") {
    a + b
  } else {
    a + "/" + b
  }
}

///| Golden 10: filter by available_toolsets
// Proto behavior: skills with requires_toolsets:[web_search] are hidden when
// available_toolsets:[] is passed and shown when available_toolsets:["web_search"] is passed.
// MoonBit now implements this correctly with the available_toolsets parameter.
test "golden: 10_filter_by_toolsets — MoonBit filters by toolsets (matches proto)" {
  let j = sg_load_golden("10_filter_by_toolsets")
  let expected = sg_expected(j)
  // Golden records proto's correct behavior
  let always_shown_with = sg_get_bool(expected, "always_shown_with")
  let always_shown_without = sg_get_bool(expected, "always_shown_without")
  let needs_websearch_hidden_without = sg_get_bool(expected, "needs_websearch_hidden_without")
  let needs_websearch_shown_with = sg_get_bool(expected, "needs_websearch_shown_with")
  assert_eq(always_shown_with, true)
  assert_eq(always_shown_without, true)
  assert_eq(needs_websearch_hidden_without, true)
  assert_eq(needs_websearch_shown_with, true)
  // Live MoonBit behavior: create temp skills matching proto's golden scenario
  let local_dir = sg_mkdtemp("golden-skills-toolsets-")
  // skill-always: no toolset restriction
  sg_mkdir(sg_path_join(local_dir, "skill-always"))
  sg_write_file(
    sg_path_join(sg_path_join(local_dir, "skill-always"), "SKILL.md"),
    "---\nname: skill-always\ndescription: Always visible skill\n---\n\n# Always\nNo restrictions.\n",
  )
  // skill-needs-websearch: requires web_search toolset
  sg_mkdir(sg_path_join(local_dir, "skill-needs-websearch"))
  sg_write_file(
    sg_path_join(sg_path_join(local_dir, "skill-needs-websearch"), "SKILL.md"),
    "---\nname: skill-needs-websearch\ndescription: Requires web_search toolset\nrequires_toolsets: [web_search]\n---\n\n# Web Search Skill\n",
  )
  let store = skills_store_load(local_dir, [])
  // With web_search toolset: both skills shown
  let with_toolset = skills_store_list(
    store, Some([]), None, available_toolsets=Some(["web_search"]),
  )
  // Without web_search toolset: only skill-always shown
  let without_toolset = skills_store_list(
    store, Some([]), None, available_toolsets=Some([]),
  )
  sg_rmrf(local_dir)
  // Verify MoonBit matches proto's golden expectations
  assert_eq(sg_find_by_name(with_toolset, "skill-always"), always_shown_with)
  assert_eq(sg_find_by_name(without_toolset, "skill-always"), always_shown_without)
  assert_eq(sg_find_by_name(with_toolset, "skill-needs-websearch"), needs_websearch_shown_with)
  assert_eq(!sg_find_by_name(without_toolset, "skill-needs-websearch"), needs_websearch_hidden_without)
}

///| Golden 09: external dirs precedence — local skill wins on name collision
test "golden: 09_external_dirs_precedence — local dir wins on name collision" {
  let j = sg_load_golden("09_external_dirs_precedence")
  let expected = sg_expected(j)
  let local_wins = sg_get_bool(expected, "local_wins")
  let count = sg_get_int(expected, "skill_foo_count")
  assert_eq(count, 1)
  assert_eq(local_wins, true)
}