///|
let base_kind_arg : @admiral.PositionDef[String] = @admiral.position_string(
  "base-kind",
  description="claude, cursor, or codex",
)

///|
fn dev_marketplace_sync_command_def(
  run : async (@admiral.Context) -> Unit,
) -> @admiral.CommandDef {
  @admiral.command(
    name="sync",
    description="Regenerate marketplace manifests from a base kind",
    positionals=[base_kind_arg],
    run=Some(run),
  )
}

///|
struct DevMarketplaceSyncOptions {
  kind : MarketplaceKind
}

///|
struct ParsedMarketplace {
  name : String
  plugins : Array[NormalizedPlugin]
}

///|
let normalized_plugin_description_lens : @lens.PresenceLens[String] = @lens.root()
  .string("description")
  .optional()

///|
let normalized_plugin_name_lens : @lens.Lens[String] = @lens.root().string(
  "name",
)

///|
let normalized_plugin_source_lens : @lens.Lens[String] = @lens.root().string(
  "source",
)

///|
impl ToJson for NormalizedPlugin with fn to_json(self) -> Json {
  let builder = @lens.JsonBuilder::JsonBuilder()
  normalized_plugin_description_lens.set_or_abort(builder, self.description)
  normalized_plugin_name_lens.set_or_abort(builder, self.name)
  normalized_plugin_source_lens.set_or_abort(builder, self.source)
  builder.to_json()
}

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

///|
struct CodexMarketplacePolicy {
  authentication : String
  installation : String
}

///|
let codex_policy_authentication_lens : @lens.Lens[String] = @lens.root().string(
  "authentication",
)

///|
let codex_policy_installation_lens : @lens.Lens[String] = @lens.root().string(
  "installation",
)

///|
impl ToJson for CodexMarketplacePolicy with fn to_json(self) -> Json {
  let builder = @lens.JsonBuilder::JsonBuilder()
  codex_policy_authentication_lens.set_or_abort(builder, self.authentication)
  codex_policy_installation_lens.set_or_abort(builder, self.installation)
  builder.to_json()
}

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

///|
struct CodexMarketplaceSource {
  path : String
  source : String
}

///|
let codex_source_path_lens : @lens.Lens[String] = @lens.root().string("path")

///|
let codex_source_source_lens : @lens.Lens[String] = @lens.root().string(
  "source",
)

///|
impl ToJson for CodexMarketplaceSource with fn to_json(self) -> Json {
  let builder = @lens.JsonBuilder::JsonBuilder()
  codex_source_path_lens.set_or_abort(builder, self.path)
  codex_source_source_lens.set_or_abort(builder, self.source)
  builder.to_json()
}

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

///|
struct CodexMarketplacePlugin {
  category : String
  name : String
  policy : CodexMarketplacePolicy
  source : CodexMarketplaceSource
}

///|
let codex_plugin_category_lens : @lens.Lens[String] = @lens.root().string(
  "category",
)

///|
let codex_plugin_name_lens : @lens.Lens[String] = @lens.root().string("name")

///|
let codex_plugin_policy_lens : @lens.Lens[Json] = @lens.root().json("policy")

///|
let codex_plugin_source_lens : @lens.Lens[Json] = @lens.root().json("source")

///|
impl ToJson for CodexMarketplacePlugin with fn to_json(self) -> Json {
  let builder = @lens.JsonBuilder::JsonBuilder()
  codex_plugin_category_lens.set_or_abort(builder, self.category)
  codex_plugin_name_lens.set_or_abort(builder, self.name)
  codex_plugin_policy_lens.set_or_abort(builder, self.policy.to_json())
  codex_plugin_source_lens.set_or_abort(builder, self.source.to_json())
  builder.to_json()
}

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

///|
enum MarketplacePlugin {
  DefaultMarketplacePlugin(NormalizedPlugin)
  CodexPlugin(CodexMarketplacePlugin)
}

///|
impl ToJson for MarketplacePlugin with fn to_json(self) -> Json {
  match self {
    DefaultMarketplacePlugin(plugin) => plugin.to_json()
    CodexPlugin(plugin) => plugin.to_json()
  }
}

///|
struct MarketplaceDocument {
  name : String
  plugins : Array[MarketplacePlugin]
}

///|
let marketplace_document_name_lens : @lens.Lens[String] = @lens.root().string(
  "name",
)

///|
let marketplace_document_plugins_lens : @lens.Lens[Json] = @lens.root().json(
  "plugins",
)

///|
impl ToJson for MarketplaceDocument with fn to_json(self) -> Json {
  let builder = @lens.JsonBuilder::JsonBuilder()
  marketplace_document_name_lens.set_or_abort(builder, self.name)
  marketplace_document_plugins_lens.set_or_abort(
    builder,
    self.plugins.to_json(),
  )
  builder.to_json()
}

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

///|
fn dev_marketplace_sync_options(
  context : @admiral.Context,
) -> DevMarketplaceSyncOptions raise {
  let value = context.get_string_required(base_kind_arg)
  let kind = marketplace_kind_from_string(value)
  { kind, }
}

///|
fn parse_marketplace(
  text : String,
  kind : MarketplaceKind,
) -> ParsedMarketplace {
  {
    name: marketplace_name_from_json(text),
    plugins: normalized_plugins_from_json(text, kind),
  }
}

///|
fn marketplace_json(
  marketplace : ParsedMarketplace,
  kind : MarketplaceKind,
) -> String {
  let plugins : Array[MarketplacePlugin] = marketplace.plugins
    .iter()
    .map(fn(plugin) { marketplace_plugin(plugin, kind) })
    .collect()
  let document : MarketplaceDocument = { name: marketplace.name, plugins }
  document.to_json().stringify(indent=2) + "\n"
}

///|
fn marketplace_plugin(
  plugin : NormalizedPlugin,
  kind : MarketplaceKind,
) -> MarketplacePlugin {
  match kind {
    Codex =>
      CodexPlugin({
        category: "Productivity",
        name: plugin.name,
        policy: {
          authentication: "ON_INSTALL",
          installation: "INSTALLED_BY_DEFAULT",
        },
        source: { path: "./\{plugin.source}", source: "local" },
      })
    _ =>
      DefaultMarketplacePlugin({
        name: plugin.name,
        source: "./\{plugin.source}",
        description: plugin.description,
      })
  }
}

///|
async fn write_json_file(path : String, text : String) -> Unit {
  let parent = @path.Path::dirname(path).to_string()
  if !@fs.exists(parent) {
    @fs.mkdir(parent, recursive=true)
  }
  @fs.write_file(path, text, create_mode=CreateOrTruncate)
}

///|
async fn sync_plugin_json(
  repo_dir : String,
  base_kind : MarketplaceKind,
  target_kind : MarketplaceKind,
  plugin_source : String,
) -> Unit {
  let base_path = (repo_dir
  |> @path.Path::join(plugin_source)
  |> @path.Path::join(marketplace_config_dir(base_kind))
  |> @path.Path::join("plugin.json")).to_string()
  if @fs.exists(base_path) {
    let target_path = (repo_dir
    |> @path.Path::join(plugin_source)
    |> @path.Path::join(marketplace_config_dir(target_kind))
    |> @path.Path::join("plugin.json")).to_string()
    write_json_file(target_path, @fs.read_file(base_path).text())
  }
}

///|
async fn sync_marketplace_target(
  repo_dir : String,
  base_kind : MarketplaceKind,
  target_kind : MarketplaceKind,
  marketplace : ParsedMarketplace,
) -> Unit {
  let path = @path.Path::join(repo_dir, marketplace_path(target_kind)).to_string()
  println(
    "Generating \{marketplace_kind_to_string(target_kind)} marketplace...",
  )
  let plugin_sources = marketplace.plugins
    .iter()
    .map(plugin => plugin.source)
    .fold(init=[], fn(sources, source) {
      if sources.contains(source) {
        sources
      } else {
        sources.iter().concat([source].iter()).collect()
      }
    })
  let tasks : Array[async () -> Unit] = [
      () => write_json_file(path, marketplace_json(marketplace, target_kind)),
    ]
    .iter()
    .concat(
      plugin_sources
      .iter()
      .map(source => {
        () => sync_plugin_json(repo_dir, base_kind, target_kind, source)
      }),
    )
    .collect()
  @async.all(tasks) |> ignore
}

///|
async fn run_dev_marketplace_sync(context : @admiral.Context) -> Unit {
  let options = dev_marketplace_sync_options(context)
  let kind = options.kind
  let repo_dir = current_working_dir()
  let info = match detect_marketplace(repo_dir) {
    Some(info) => info
    None => fail("No supported marketplace found")
  }
  guard info.kind == kind else {
    fail("base marketplace \{marketplace_kind_to_string(kind)} not found")
  }
  let marketplace = parse_marketplace(@fs.read_file(info.path).text(), kind)
  let target_kinds = marketplace_kinds()
    .iter()
    .filter(target_kind => target_kind != kind)
    .collect()
  @async.all(
    target_kinds.map(target_kind => {
      () => sync_marketplace_target(repo_dir, kind, target_kind, marketplace)
    }),
  )
  |> ignore
  println("Marketplace sync complete.")
}