///|
/// The host owns records; the console only filters, ranks and returns them.
priv struct Record {
  id : String
  score : Int32
  tags : Array[String]
}

///|
fn select_records(
  line : String,
  records : Array[Record],
) -> Json raise @docopt.DocoptError {
  let p = @docopt.Parser::new(
    "Usage: console select [--min-score=N] [--tag=T]... [--limit=N]\nOptions:\n  --min-score=N  Minimum score [default: 0]\n  --tag=T  Require each tag\n  --limit=N  Maximum rows [default: 10]",
  )
  let v = p.parse_line(line)
  let minimum = v.integer("--min-score", minimum=0, maximum=100)
  let limit = v.integer("--limit", minimum=1, maximum=1000)
  let tags = v.texts("--tag")
  let filtered = []
  for record in records {
    let mut accept = record.score >= minimum
    for tag in tags {
      if !record.tags.contains(tag) {
        accept = false
      }
    }
    if accept {
      filtered.push(record)
    }
  }
  filtered.sort_by(fn(a, b) {
    if a.score != b.score {
      b.score.compare(a.score)
    } else {
      a.id[:].lexical_compare(b.id[:])
    }
  })
  let rows = []
  for i, row in filtered {
    if i >= limit {
      break
    }
    rows.push(({ "id": Json(row.id), "score": Json(row.score) } : Json))
  }
  { "matched": Json(filtered.length()), "rows": Json::array(rows) }
}

///|
fn sample_records() -> Array[Record] {
  [
    { id: "parser", score: 90, tags: ["core", "cli"], },
    { id: "lexer", score: 80, tags: ["core"], },
    { id: "theme", score: 95, tags: ["ui"], },
  ]
}

///|
fn main {
  let args = @cliargs.arguments()
  // Host shells have already split argv. Requoting preserves exact boundaries.
  try {
    let line = if args.is_empty() {
      "select --min-score=50 --tag=core --limit=1"
    } else {
      @docopt.join_command(args)
    }
    println(select_records(line, sample_records()).stringify())
  } catch {
    e => println("error: " + e.message())
  }
}