query_maps <- list(
  view = c(
    overview = "overview",
    graph_family = "graph-family",
    density = "density",
    centrality = "centrality",
    thresholds = "thresholds",
    total_failure = "total-failure",
    provenance = "study"
  ),
  density = c("Sparse" = "S", "Denser" = "D"),
  metric = c(
    degree = "degree",
    strength = "strength",
    closeness = "closeness",
    betweenness = "betweenness"
  ),
  density_view = c(contrast = "contrast", extent = "extent"),
  threshold_view = c(contrast = "contrast", extent = "extent"),
  failure_view = c(completion = "completion", steps = "steps"),
  threshold = c(
    "0.10-0.20" = "1",
    "0.20-0.40" = "2",
    "0.40-0.60" = "3",
    "0.60-0.80" = "4"
  )
)

query_defaults <- list(
  view = "overview",
  density = "Denser",
  metric = "strength",
  threshold = "0.20-0.40",
  density_view = "contrast",
  threshold_view = "contrast",
  failure_view = "completion"
)

encode_query_value <- function(value, map) {
  encoded <- unname(map[value])
  if (length(encoded) == 0L || is.na(encoded[[1]])) "" else encoded[[1]]
}

decode_query_value <- function(value, map, fallback) {
  if (is.null(value) || !nzchar(value)) return(fallback)
  requested <- strsplit(value, ",", fixed = TRUE)[[1]][[1]]
  inverse <- stats::setNames(names(map), unname(map))
  decoded <- unname(inverse[requested])
  if (length(decoded) == 0L || is.na(decoded[[1]])) fallback else decoded[[1]]
}

parse_app_query <- function(search) {
  query <- shiny::parseQueryString(search)
  list(
    view = decode_query_value(query$view, query_maps$view, query_defaults$view),
    density = decode_query_value(query$density, query_maps$density, query_defaults$density),
    metric = decode_query_value(query$metric, query_maps$metric, query_defaults$metric),
    threshold = decode_query_value(query$threshold, query_maps$threshold, query_defaults$threshold),
    density_view = decode_query_value(query$density_view, query_maps$density_view, query_defaults$density_view),
    threshold_view = decode_query_value(query$threshold_view, query_maps$threshold_view, query_defaults$threshold_view),
    failure_view = decode_query_value(query$failure_view, query_maps$failure_view, query_defaults$failure_view)
  )
}

build_app_query <- function(input) {
  values <- list(
    sv = "3",
    view = encode_query_value(input$view, query_maps$view),
    density = encode_query_value(input$density, query_maps$density),
    metric = encode_query_value(input$metric, query_maps$metric),
    threshold = encode_query_value(input$threshold, query_maps$threshold),
    density_view = encode_query_value(input$density_view, query_maps$density_view),
    threshold_view = encode_query_value(input$threshold_view, query_maps$threshold_view),
    failure_view = encode_query_value(input$failure_view, query_maps$failure_view)
  )
  paste0(
    "?",
    paste(
      paste0(names(values), "=", vapply(values, utils::URLencode, "", reserved = TRUE)),
      collapse = "&"
    )
  )
}

restore_query_state <- function(session, search) {
  state <- parse_app_query(search)
  bslib::nav_select("view", selected = state$view, session = session)
  shiny::updateSelectInput(session, "density", selected = state$density)
  shiny::updateSelectInput(session, "metric", selected = state$metric)
  shiny::updateSelectInput(session, "threshold", selected = state$threshold)
  shiny::updateRadioButtons(session, "density_view", selected = state$density_view)
  shiny::updateRadioButtons(session, "threshold_view", selected = state$threshold_view)
  shiny::updateRadioButtons(session, "failure_view", selected = state$failure_view)
  invisible(state)
}
