///|

///|
struct LockPlugin {
  name : String
  path : String
  enabled_skills : Array[String]
}

///|
struct LockRepository {
  source_type : RepositorySourceType
  source : String
  marketplace_kind : MarketplaceKind
  commit_hash : String?
  plugins : Array[LockPlugin]
}

///|
enum RepositorySourceType {
  RepositoryLocal
  RepositoryGithub
}

///|
impl ToJson for LockPlugin with fn to_json(self) -> Json {
  Json::object({
    "name": self.name.to_json(),
    "path": self.path.to_json(),
    "enabledSkills": self.enabled_skills.to_json(),
  })
}

///|
impl ToJson for LockRepository with fn to_json(self) -> Json {
  let object : Map[String, Json] = Map([])
  object.set(
    "sourceType",
    repository_source_type_to_string(self.source_type).to_json(),
  )
  object.set("source", self.source.to_json())
  match self.commit_hash {
    Some(value) => object.set("commitHash", value.to_json())
    None => ()
  }
  object.set(
    "marketplaceKind",
    marketplace_kind_to_string(self.marketplace_kind).to_json(),
  )
  object.set("plugins", self.plugins.to_json())
  Json::object(object)
}

///|
impl ToJson for LockFile with fn to_json(self) -> Json {
  Json::object({
    "version": Json::number(1.0, repr="1"),
    "skillDirs": self.skill_dirs.to_json(),
    "repositories": self.repositories.to_json(),
  })
}

///|
extend LockFile with ToJson::{to_json}

///|
fn repository_source_type_from_string(
  value : String,
) -> RepositorySourceType raise {
  match value {
    "local" => RepositoryLocal
    "github" => RepositoryGithub
    _ => fail("unsupported repository source type: \{value}")
  }
}

///|
fn repository_source_type_to_string(
  source_type : RepositorySourceType,
) -> String {
  match source_type {
    RepositoryLocal => "local"
    RepositoryGithub => "github"
  }
}

///|
fn empty_lock_file() -> LockFile {
  { skill_dirs: [], repositories: [] }
}

///|
fn lock_plugin_from_json(json : Json) -> LockPlugin? {
  let _ = @lens.root().get(json) catch { _ => return None }
  let name = @lens.root().string("name").get(json) catch { _ => "plugin" }
  let path = @lens.root().string("path").get(json) catch { _ => name }
  let enabled_skills_json = @lens.root().json("enabledSkills").array().get(json) catch {
      _ => []
    }
  let enabled_skills = strings_from_json_array(enabled_skills_json)
  Some({ name, path, enabled_skills })
}

///|
fn strings_from_json_array(values : Array[Json]) -> Array[String] {
  values
  .iter()
  .filter_map(value => {
    match value {
      Json::String(text) => Some(text)
      _ => None
    }
  })
  .collect()
}

///|
fn lock_plugins_from_json(json : Array[Json]) -> Array[LockPlugin] {
  json.iter().filter_map(lock_plugin_from_json).collect()
}

///|
fn repositories_from_json_array(values : Array[Json]) -> Array[LockRepository] {
  values
  .iter()
  .filter_map(fn(value) { lock_repository_from_json(value) catch { _ => None } })
  .collect()
}

///|
fn lock_repository_from_json(json : Json) -> LockRepository? raise {
  let _ = @lens.root().get(json) catch { _ => return None }
  let source = @lens.root().string("source").get(json) catch { _ => "" }
  guard source != "" else { return None }
  let source_type = @lens.root().string("sourceType").get(json) catch {
      _ => "local"
    }
  let marketplace_kind = @lens.root().string("marketplaceKind").get(json) catch {
      _ => "claude"
    }
  let commit_hash = @lens.root().string("commitHash").optional().get(json) catch {
      _ => None
    }
  let plugins_json = @lens.root().json("plugins").array().get(json) catch {
      _ => []
    }
  let plugins = lock_plugins_from_json(plugins_json)
  Some({
    source_type: repository_source_type_from_string(source_type),
    source,
    marketplace_kind: marketplace_kind_from_string(marketplace_kind),
    commit_hash,
    plugins,
  })
}

///|
struct LockFile {
  skill_dirs : Array[String]
  repositories : Array[LockRepository]
}

///|
fn empty_lock_text() -> String {
  empty_lock_file().to_json().stringify(indent=2) + "\n"
}

///|
async fn ensure_lock(agents : String) -> Unit {
  if !@fs.exists(agents) {
    @fs.mkdir(agents, recursive=true)
  }
  let path = lock_path_from_agents(agents)
  if !@fs.exists(path) {
    @fs.write_file(path, empty_lock_text(), create_mode=CreateOrTruncate)
  }
}

///|
async fn read_lock(agents : String) -> LockFile {
  let path = lock_path_from_agents(agents)
  let text = if @fs.exists(path) {
    @fs.read_file(path).text()
  } else {
    empty_lock_text()
  }
  let document = @json.parse(text) catch { _ => return empty_lock_file() }
  let version = @lens.root().number("version").get(document) catch { _ => 0.0 }
  guard version == 1.0 else { return empty_lock_file() }
  let skill_dirs_json = @lens.root().json("skillDirs").array().get(document) catch {
      _ => []
    }
  let repositories_json = @lens.root()
    .json("repositories")
    .array()
    .get(document) catch {
      _ => []
    }
  let skill_dirs = strings_from_json_array(skill_dirs_json)
  let repositories = repositories_from_json_array(repositories_json)
  { skill_dirs, repositories }
}

///|
async fn write_lock(agents : String, lock : LockFile) -> Unit {
  @fs.write_file(
    lock_path_from_agents(agents),
    lock.to_json().stringify(indent=2) + "\n",
    create_mode=CreateOrTruncate,
  )
}