/**
  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  Copyright 2021, Yahoo Inc.
*/

#include <string>

#include <swoc/TextView.h>
#include <swoc/Errata.h>
#include <swoc/BufferWriter.h>
#include <swoc/bwf_ex.h>

#include "txn_box/common.h"
#include "txn_box/Config.h"
#include "txn_box/Context.h"
#include "txn_box/Extractor.h"
#include "txn_box/Directive.h"
#include "txn_box/Comparison.h"

#include "txn_box/ts_util.h"

using swoc::BufferWriter;
using swoc::Errata;
using swoc::Rv;
using swoc::TextView;
using namespace swoc::literals;

/* ------------------------------------------------------------------------------------ */
namespace
{
/// Standard caseless compare arg.
static inline constexpr TextView ARG_NOCASE = "nc";
/// Standard reverse order arg.
static inline constexpr TextView ARG_REVERSE = "rev";

struct QPair {
  static constexpr char Sep = '=';
  using self_type           = QPair;

  TextView   name;
  TextView   value;
  char       elt_sep = 0;   ///< Separator before name.
  char       kv_sep  = Sep; ///< Separator for name/value - always '=' if not null.
  self_type *_next   = nullptr;
  self_type *_prev   = nullptr;
  using Linkage      = swoc::IntrusiveLinkage<self_type, &self_type::_next, &self_type::_prev>;

  QPair() = default;
  QPair(TextView k, TextView v) : name(k), value(v) {}

  TextView
  all() const
  {
    return TextView(name.data(), value.data_end());
  }
};
QPair
query_take_qpair(TextView &qs)
{
  char elt_sep = 0;
  // Strip the leading separator, if any, and drop empty elements.
  // Need to do this to track what was there originally and try to re-use it.
  while (qs) {
    if (auto c = *qs; c == '&' || c == ';') {
      qs.remove_prefix(1);
      elt_sep = c;
    } else
      break;
  }

  // If there's anything left, look for kv pair.
  if (qs) {
    auto  v{qs.clip_prefix_of([](char c) { return c != '&' && c != ';'; })};
    auto  k = v.take_prefix_at(QPair::Sep);
    QPair zret{k, v};
    zret.elt_sep = elt_sep;
    return zret;
  }
  return {};
}

TextView
query_value_update(Context &ctx, TextView qs, TextView name, Feature const &value, bool case_p, bool force_equal_p)
{
  TextView zret;
  bool     nv_is_nil_p = (value.value_type() == NIL);
  if (qs.empty()) {
    if (!nv_is_nil_p) {
      zret = ctx.render_transient([&](BufferWriter &w) { w.print("{}={}", name, std::get<STRING>(value)); });
    }
  } else { // invariant - query string was not empty.
    auto &&[k, v] = ts::query_value_for(qs, name, case_p);
    if (k.empty()) { // not found at all.
      if (!nv_is_nil_p) {
        zret = ctx.render_transient([&](BufferWriter &w) { w.print("{}&{}={}", qs, name, value); });
      }
    } else {
      // Make a note if there was no value but an '=' anyway.
      bool equal_p = force_equal_p || (v.empty() && (v.data() != k.end()));
      // Prefix is the part before the name.
      TextView prefix{qs.data(), k.data()};
      // Suffix is the part after the value.
      TextView suffix{v.end(), qs.end()};
      if (!nv_is_nil_p) {
        zret = ctx.render_transient([&, k = k](BufferWriter &w) {
          w.write(prefix).write(k);
          if (equal_p || !(value.index() == IndexFor(STRING) && std::get<IndexFor(STRING)>(value).empty())) {
            w.print("={}", value);
          }
          w.write(suffix);
        });
      } else { // NIL, remove the pair.
        prefix.rtrim("&;"_tv);
        suffix.ltrim("&;"_tv);
        if (suffix.empty()) {
          zret = prefix;
        } else if (prefix.empty()) {
          zret = suffix;
        } else { // neither is empty.
          zret = ctx.render_transient([&](BufferWriter &w) { w.print("{}&{}", prefix, suffix); });
        }
      }
    }
  }
  return zret;
}

} // namespace
/* ------------------------------------------------------------------------------------ */
// Query string.
// These have the @c extract method because it can be done as a @c Direct
// value and that's better than running through the formatting.

class QueryValueExtractor : public Extractor
{
public:
  Rv<ActiveType> validate(Config &cfg, Spec &spec, TextView const &arg) override;
  using Extractor::extract; // declare hidden member function
  Feature extract(Context &ctx, Spec const &spec) override;

protected:
  TextView _arg;

  /// @return The key (name) for the extractor.
  virtual TextView const &key() const = 0;
  /// @return The appropriate query string.
  virtual TextView query_string(Context &ctx) const = 0;
};

Rv<ActiveType>
QueryValueExtractor::validate(Config &cfg, Spec &spec, const TextView &arg)
{
  if (arg.empty()) {
    return Errata(S_ERROR, "Extractor \"{}\" requires a key name argument.", this->key());
  }
  spec._data.text = cfg.localize(arg);
  return ActiveType{NIL, STRING};
}

Feature
QueryValueExtractor::extract(Context &ctx, const Spec &spec)
{
  auto qs = this->query_string(ctx);
  if (qs.empty()) {
    return NIL_FEATURE;
  }
  auto &&[name, value] = ts::query_value_for(qs, spec._data.text, true); // case insensitive
  if (value.size() == 0) {
    if (value.data() == nullptr || value.data() == name.end()) {
      return NIL_FEATURE; // key not present or value not present
    }
    return FeatureView::Literal("");
  }
  return FeatureView::Direct(value);
}

// --

class Ex_ua_req_query : public StringExtractor
{
public:
  static constexpr TextView NAME{"ua-req-query"};

  using Extractor::extract; // un-hide the overloaded
  Feature       extract(Context &ctx, Spec const &spec) override;
  BufferWriter &format(BufferWriter &w, Spec const &spec, Context &ctx) override;
};

Feature
Ex_ua_req_query::extract(Context &ctx, Spec const &)
{
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    if (ts::URL url{hdr.url()}; url.is_valid()) {
      return FeatureView::Direct(url.query());
    }
  }
  return NIL_FEATURE;
}

BufferWriter &
Ex_ua_req_query::format(BufferWriter &w, Spec const &spec, Context &ctx)
{
  return bwformat(w, spec, this->extract(ctx, spec));
}

class Ex_ua_req_query_value : public QueryValueExtractor
{
public:
  static constexpr TextView NAME{"ua-req-query-value"};

protected:
  TextView const &key() const override;
  TextView        query_string(Context &ctx) const override;
};

TextView const &
Ex_ua_req_query_value::key() const
{
  return NAME;
}

TextView
Ex_ua_req_query_value::query_string(Context &ctx) const
{
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    if (ts::URL url{hdr.url()}; url.is_valid()) {
      return url.query();
    }
  }
  return {};
}

// --

class Ex_pre_remap_query : public StringExtractor
{
public:
  static constexpr TextView NAME{"pre-remap-query"};
  using Extractor::extract; // un-hide the overloaded
  Feature       extract(Context &ctx, Spec const &spec) override;
  BufferWriter &format(BufferWriter &w, Spec const &spec, Context &ctx) override;
};

Feature
Ex_pre_remap_query::extract(Context &ctx, Spec const &)
{
  if (ts::URL url{ctx._txn.pristine_url_get()}; url.is_valid()) {
    return FeatureView::Direct(url.query());
  }
  return NIL_FEATURE;
}

BufferWriter &
Ex_pre_remap_query::format(BufferWriter &w, Spec const &spec, Context &ctx)
{
  return bwformat(w, spec, this->extract(ctx, spec));
}

class Ex_pre_remap_req_query_value : public QueryValueExtractor
{
public:
  static constexpr TextView NAME{"pre-remap-req-query-value"};

protected:
  TextView const &key() const override;
  TextView        query_string(Context &ctx) const override;
};

TextView const &
Ex_pre_remap_req_query_value::key() const
{
  return NAME;
}

TextView
Ex_pre_remap_req_query_value::query_string(Context &ctx) const
{
  if (ts::URL url{ctx._txn.pristine_url_get()}; url.is_valid()) {
    return url.query();
  }
  return {};
}

// --

class Ex_proxy_req_query : public StringExtractor
{
public:
  static constexpr TextView NAME{"proxy-req-query"};
  using Extractor::extract; // un-hide the overloaded
  Feature       extract(Context &ctx, Spec const &spec) override;
  BufferWriter &format(BufferWriter &w, Spec const &spec, Context &ctx) override;
};

Feature
Ex_proxy_req_query::extract(Context &ctx, Spec const &)
{
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    if (ts::URL url{hdr.url()}; url.is_valid()) {
      return FeatureView::Direct(url.query());
    }
  }
  return NIL_FEATURE;
}

BufferWriter &
Ex_proxy_req_query::format(BufferWriter &w, Spec const &spec, Context &ctx)
{
  return bwformat(w, spec, this->extract(ctx, spec));
}

class Ex_proxy_req_query_value : public QueryValueExtractor
{
public:
  static constexpr TextView NAME{"proxy-req-query-value"};

protected:
  TextView const &key() const override;
  TextView        query_string(Context &ctx) const override;
};

TextView const &
Ex_proxy_req_query_value::key() const
{
  return NAME;
}

TextView
Ex_proxy_req_query_value::query_string(Context &ctx) const
{
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    if (ts::URL url{hdr.url()}; url.is_valid()) {
      return url.query();
    }
  }
  return {};
}

/* ------------------------------------------------------------------------------------ */
/** Sort the query string by name.
 *
 */
class Mod_query_sort : public Modifier
{
  using self_type  = Mod_query_sort;
  using super_type = Modifier;

public:
  static inline const std::string KEY{"query-sort"};
  bool                            is_valid_for(ActiveType const &ex_type) const override;
  ActiveType                      result_type(ActiveType const &) const override;
  using super_type::operator(); // un-hide overloaded.
  Rv<Feature>       operator()(Context &ctx, feature_type_for<STRING> qs) override;
  static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value);

protected:
  bool _case_p = false; ///< Case sensitive sort.
  bool _rev_p  = false; ///< Reverse order sort.

  Mod_query_sort(bool case_p, bool rev_p) : _case_p(case_p), _rev_p(rev_p) {}
};

bool
Mod_query_sort::is_valid_for(const ActiveType &ex_type) const
{
  return ex_type.can_satisfy(STRING);
}

ActiveType
Mod_query_sort::result_type(const ActiveType &) const
{
  return {NIL, STRING};
}

Rv<Feature>
Mod_query_sort::operator()(Context &ctx, feature_type_for<STRING> qs)
{
  using list_type = swoc::IntrusiveDList<QPair::Linkage>;
  auto list       = ctx.make<list_type>();

  while (qs) {
    list->append(ctx.make<QPair>(query_take_qpair(qs)));
  }

  // Make an array of pointers to list items to sort.
  size_t length = list->count() - 1; // separators required.
  auto   sa     = ctx.alloc_span<QPair *>(list->count());
  auto   ptr    = sa.begin();
  for (auto &item : *list) {
    *ptr++  = &item;
    length += item.name.size() + item.value.size() + 1;
  }

  static auto case_sort         = [](QPair *lhs, QPair *rhs) { return strcmp(lhs->name, rhs->name) < 0; };
  static auto case_rev_sort     = [](QPair *lhs, QPair *rhs) { return strcmp(lhs->name, rhs->name) > 0; };
  static auto caseless_sort     = [](QPair *lhs, QPair *rhs) { return strcasecmp(lhs->name, rhs->name) < 0; };
  static auto caseless_rev_sort = [](QPair *lhs, QPair *rhs) { return strcasecmp(lhs->name, rhs->name) > 0; };

  auto sorter = _case_p ? (_rev_p ? case_rev_sort : case_sort) : (_rev_p ? caseless_rev_sort : caseless_sort);

  std::stable_sort(sa.begin(), sa.end(), sorter);

  swoc::FixedBufferWriter w{ctx.transient_buffer(length)};
  bool                    first_p = true;
  for (auto item : sa) {
    if (!first_p) {
      w.write(item->elt_sep);
    }
    w.print("{}{}{}", item->name, item->kv_sep, item->value);
    first_p = false;
  }
  ctx.transient_finalize(w.view().size());
  return {w.view()};
}

Rv<Modifier::Handle>
Mod_query_sort::load(Config &, YAML::Node, TextView, TextView arg, YAML::Node)
{
  bool case_p = true;
  bool rev_p  = false;
  while (arg) {
    auto token = arg.take_prefix_at(',');
    if (token == ARG_NOCASE) {
      case_p = false;
    } else if (token == ARG_REVERSE) {
      rev_p = true;
    } else {
      return Errata(S_ERROR, R"(Invalid argument "{}" in modifier "{}")", token, KEY);
    }
  }
  return Handle{new self_type(case_p, rev_p)};
}

// ---

class Mod_query_filter : public FilterMod
{
  using self_type  = Mod_query_filter;
  using super_type = Modifier;

public:
  static inline const std::string KEY{"query-filter"};

  static inline const std::string OPT_VALUE         = "value";         ///< Compare value
  static inline const std::string OPT_APPEND        = "append";        ///< Append element.
  static inline const std::string OPT_APPEND_UNIQUE = "append-unique"; ///< Append element uniquely.
  static inline const std::string OPT_PASS_REST     = "pass-rest";     ///< Pass all remainining elements.
  static inline const std::string OPT_DROP_REST     = "drop-rest";     ///< Drop all remainining elements.

  static inline const std::string PAIR_VALUE = "value"; ///< Element value.
  static inline const std::string PAIR_NAME  = "name";  ///< Element name.

  static constexpr TextView CFG_STORE_KEY = "mod-query-filter";
  using cfg_store_type                    = ReservedSpan;

  bool       is_valid_for(ActiveType const &ex_type) const override;
  ActiveType result_type(ActiveType const &) const override;
  using super_type::operator(); // un-hide overloaded.
  Rv<Feature>       operator()(Context &ctx, feature_type_for<STRING> qs) override;
  static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value);

  /// Local extractor for name in a pair.
  class Ex_Name : public Extractor
  {
    virtual swoc::Rv<ActiveType> validate(Config &cfg, Spec &spec, swoc::TextView const &) override;
    using Extractor::extract; // declare hidden member function
    Feature extract(Context &ctx, Spec const &spec) override;
  };
  /// Local extractor for value in a pair.
  class Ex_Value : public Extractor
  {
    virtual swoc::Rv<ActiveType> validate(Config &cfg, Spec &spec, swoc::TextView const &) override;
    using Extractor::extract; // declare hidden member function
    Feature extract(Context &ctx, Spec const &spec) override;
  };

protected:
  /// A filter comparison case.
  struct Case {
    Action _action = PASS; ///< Action on match.

    Comparison::Handle _cmp;       ///< Comparison.
    Comparison::Handle _value_cmp; ///< Comparison for value.

    /// Expressions for name and/or value.
    struct PairExpr {
      Expr _name;
      Expr _value;
    };

    PairExpr _replace; ///< Replacement.

    swoc::MemSpan<PairExpr> _append; ///< Elements to append.

    /// Option for the rest of the elements.
    enum { REST_NONE, REST_PASS, REST_DROP } _opt_rest = REST_NONE;

    void assign(Comparison::Handle &&handle);

    bool operator()(Context &ctx, Feature const &feature);

    Errata pre_load(Config &cfg, YAML::Node node);

    Errata parse_pair(Config &cfg, YAML::Node node, PairExpr &pair);
    void   eval_pair(Context &ctx, PairExpr const &pe, QPair &qp) const;
  };

  /// Container for cases with comparisons.
  ComparisonGroup<Case> _cases;

  Mod_query_filter() = default;
  Case const *compare(Context &ctx, QPair const &qp) const;

  static inline Ex_Name  ex_name;
  static inline Ex_Value ex_value;
  /// Table for local extactors.
  static Extractor::Table _ex_table;
};
Extractor::Table Mod_query_filter::_ex_table{
  {"name",  &Mod_query_filter::ex_name },
  {"value", &Mod_query_filter::ex_value}
};

swoc::Rv<ActiveType>
Mod_query_filter::Ex_Name::validate(Config &cfg, Extractor::Spec &spec, TextView const &)
{
  spec._data.ctx_reserved_span = *(cfg.named_object<cfg_store_type>(CFG_STORE_KEY));
  return ActiveType{NIL, STRING};
}

Feature
Mod_query_filter::Ex_Name::extract(Context &ctx, Extractor::Spec const &spec)
{
  auto qp = ctx.storage_for(spec._data.ctx_reserved_span).rebind<QPair *>()[0];
  return FeatureView::Literal(qp->name);
}

swoc::Rv<ActiveType>
Mod_query_filter::Ex_Value::validate(Config &cfg, Extractor::Spec &spec, TextView const &)
{
  spec._data.ctx_reserved_span = *(cfg.named_object<cfg_store_type>(CFG_STORE_KEY));
  return ActiveType{NIL, STRING};
}

Feature
Mod_query_filter::Ex_Value::extract(Context &ctx, Extractor::Spec const &spec)
{
  auto qp = ctx.storage_for(spec._data.ctx_reserved_span).rebind<QPair *>()[0];
  return FeatureView::Literal(qp->value);
}

bool
Mod_query_filter::is_valid_for(const ActiveType &ex_type) const
{
  return ex_type.can_satisfy(STRING);
}

ActiveType
Mod_query_filter::result_type(const ActiveType &) const
{
  return {NIL, STRING};
}

auto
Mod_query_filter::compare(Context &ctx, QPair const &qp) const -> Case const *
{
  using fstr = feature_type_for<STRING>;
  for (auto const &c : _cases) {
    if ((!c._cmp || (*c._cmp)(ctx, fstr(qp.name))) && (!c._value_cmp || (*c._value_cmp)(ctx, fstr(qp.value)))) {
      return &c;
    }
  }
  return nullptr;
}

void
Mod_query_filter::Case::assign(Comparison::Handle &&handle)
{
  _cmp = std::move(handle);
}

swoc::Errata
Mod_query_filter::Case::parse_pair(Config &cfg, YAML::Node node, PairExpr &pair)
{
  if (!node.IsMap()) {
    return Errata(S_ERROR, "Element at {} is not an object as required.", node.Mark());
  }

  if (auto knode = node[PAIR_NAME]; knode) {
    auto &&[expr, errata]{cfg.parse_expr(knode)};
    if (!errata.is_ok()) {
      errata.note("While parsing expression for {}.", PAIR_NAME);
    }
    pair._name = std::move(expr);
  }

  if (auto vnode = node[PAIR_VALUE]; vnode) {
    auto &&[expr, errata]{cfg.parse_expr(vnode)};
    if (!errata.is_ok()) {
      errata.note("While parsing expression for {}.", PAIR_VALUE);
    }
    pair._value = std::move(expr);
  }

  return {};
}

Errata
Mod_query_filter::Case::pre_load(Config &cfg, YAML::Node cmp_node)
{
  unsigned action_count = 0;

  if (!cmp_node.IsMap()) {
    return Errata(S_ERROR, "List element at {} for {} modifier is not a comparison object.", cmp_node.Mark(), KEY);
  }

  if (auto do_node = cmp_node[Global::DO_KEY]; do_node) {
    return Errata(S_ERROR, R"("{}" at line {} is not allowed in a modifier comparison.)", Global::DO_KEY, do_node.Mark());
  }

  YAML::Node drop_node = cmp_node[ACTION_DROP];
  if (drop_node) {
    _action = DROP;
    cmp_node.remove(ACTION_DROP);
    ++action_count;
  }

  if (YAML::Node pass_node = cmp_node[ACTION_PASS]; pass_node) {
    _action = PASS;
    cmp_node.remove(ACTION_PASS);
    ++action_count;
  }

  if (YAML::Node replace_node = cmp_node[ACTION_REPLACE]; replace_node) {
    Errata errata = this->parse_pair(cfg, replace_node, _replace);
    if (!errata.is_ok()) {
      errata.note("While parsing expression at {} for {} key in comparison at {}.", replace_node.Mark(), ACTION_REPLACE,
                  cmp_node.Mark());
      return errata;
    }
    _action = REPLACE;
    cmp_node.remove(ACTION_REPLACE);
    ++action_count;
  }

  if (action_count > 1) {
    return Errata(S_ERROR, "Only one of {}, {}, {} is allowed in the {} comparison at {}.", ACTION_REPLACE, ACTION_DROP,
                  ACTION_PASS, KEY, cmp_node.Mark());
  }

  YAML::Node opt_node = cmp_node[ACTION_OPT];
  if (opt_node) {
    if (!opt_node.IsMap()) {
      return Errata(S_ERROR, R"("Value for "{}" at {} for "{}" modifier is not an object.)", ACTION_OPT, opt_node.Mark(), KEY);
    }

    if (YAML::Node vcmp_node = opt_node[OPT_VALUE]; vcmp_node) {
      auto &&[vcmp, vcmp_errata]{Comparison::load(cfg, vcmp_node)};
      if (!vcmp_errata.is_ok()) {
        return std::move(vcmp_errata.note(R"(While parsing "{}" option for "{}" modifier)", OPT_VALUE, KEY));
      }
      _value_cmp = std::move(vcmp);
      opt_node.remove(vcmp_node);
    }

    Errata errata;
    if (YAML::Node append_node = opt_node[OPT_APPEND]; append_node) {
      if (append_node.IsSequence()) {
        auto span = cfg.alloc_span<PairExpr>(append_node.size());
        for (unsigned idx = 0; idx < append_node.size() && errata.is_ok(); ++idx) {
          auto &p{span[idx]};
          new (&p) PairExpr;
          errata = this->parse_pair(cfg, append_node[idx], p);
        }
      } else {
        auto span = cfg.alloc_span<PairExpr>(1);
        new (&span[0]) PairExpr;
        errata = this->parse_pair(cfg, append_node, span[0]);
      }
      opt_node.remove(append_node);
    }
    if (!errata.is_ok()) {
      errata.note("While parsing {} expressions.", OPT_APPEND);
      return errata;
    }

    if (auto pass_rest_node = opt_node[OPT_PASS_REST]; pass_rest_node) {
      _opt_rest = Case::REST_PASS;
      opt_node.remove(pass_rest_node);
    }

    if (auto drop_rest_node = opt_node[OPT_DROP_REST]; drop_rest_node) {
      if (_opt_rest != Case::REST_NONE) {
        return Errata(S_ERROR, "{} at {} has both {} and {} which is not allowed.", ACTION_OPT, opt_node.Mark(), OPT_PASS_REST,
                      OPT_DROP_REST);
      }
      _opt_rest = Case::REST_DROP;
      opt_node.remove(drop_rest_node);
    }

    cmp_node.remove(opt_node);
  }

  return {};
}

Rv<Modifier::Handle>
Mod_query_filter::load(Config &cfg, YAML::Node node, TextView, TextView, YAML::Node key_value)
{
  auto   self = new self_type;
  Handle handle(self);
  let    local_ex_scope{cfg._local_extractors, &_ex_table};

  // Need reserved context storage to pass the current @c QPair down to nested extractors.
  // The reserved span is stored in the configuration and then used at run time.
  auto ctx_store = cfg.obtain_named_object<cfg_store_type>(CFG_STORE_KEY);
  if (ctx_store->n == 0) {
    *ctx_store = cfg.reserve_ctx_storage(sizeof(QPair *));
  }

  if (auto errata = self->_cases.load(cfg, key_value); !errata.is_ok()) {
    errata.note(R"(While parsing modifier "{}" at line {}.)", KEY, node.Mark());
    return errata;
  }

  return handle;
}

void
Mod_query_filter::Case::eval_pair(Context &ctx, const PairExpr &pe, QPair &qp) const
{
  if (!pe._name.empty()) {
    auto name = ctx.extract(pe._name);
    if (!is_nil(name)) {
      qp.name = ctx.render_transient([&](BufferWriter &w) { w.print("{}", name); });
      ctx.commit_transient();
    }
  }

  if (!pe._value.empty()) {
    auto value = ctx.extract(pe._value);
    if (!is_nil(value)) {
      qp.value = ctx.render_transient([&](BufferWriter &w) { w.print("{}", value); });
      ctx.commit_transient();
    }
  }
}

Rv<Feature>
Mod_query_filter::operator()(Context &ctx, feature_type_for<STRING> qs)
{
  using list_type = swoc::IntrusiveDList<QPair::Linkage>;
  auto    list    = ctx.make<list_type>();
  QPair *&qp_ex   = ctx.storage_for(*ctx.cfg().named_object<cfg_store_type>(CFG_STORE_KEY)).rebind<QPair *>()[0];

  while (qs) {
    auto   qp     = query_take_qpair(qs);
    auto   c      = this->compare(ctx, qp);
    Action action = (c ? c->_action : DROP);
    qp_ex         = &qp; // pass to potential extractors.
    switch (action) {
    case DROP:
      break;
    case PASS:
      list->append(ctx.make<QPair>(qp));
      break;
    case REPLACE:
      c->eval_pair(ctx, c->_replace, qp);
      list->append(ctx.make<QPair>(qp));
      break;
    }

    // Append action.
    for (auto &pe : c->_append) {
      QPair q_pair;
      c->eval_pair(ctx, pe, q_pair);
      list->append(ctx.make<QPair>(q_pair));
    }

    // drop-rest, pass-rest actions.
    if (c->_opt_rest != Case::REST_NONE) {
      if (c->_opt_rest == Case::REST_DROP) {
        qs.clear();
      }
      break;
    }
  }

  // Consolidate list into a new query string.
  size_t length = qs.size() + list->count() + 1; // separators.
  for (auto &p : *list) {
    length += p.name.size() + p.value.size() + 1;
  }

  swoc::FixedBufferWriter w{ctx.transient_buffer(length)};
  bool                    first_p = true;
  for (auto &item : *list) {
    if (!first_p) {
      w.write(item.elt_sep);
    }
    w.print("{}{}{}", item.name, swoc::bwf::Optional("{}", item.kv_sep), item.value);
    first_p = false;
  }
  w.write(qs);
  ctx.transient_finalize(w.view().size());
  return {FeatureView(w.view())};
}
/* ------------------------------------------------------------------------------------ */
/** Set the query for the request.
 */
class Do_ua_req_query : public Directive
{
  using super_type = Directive;       ///< Parent type.
  using self_type  = Do_ua_req_query; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  explicit Do_ua_req_query(Expr &&expr);

  Errata invoke(Context &ctx) override;

  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature.
};

const std::string Do_ua_req_query::KEY{"ua-req-query"};
const HookMask    Do_ua_req_query::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_query::Do_ua_req_query(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_query::invoke(Context &ctx)
{
  TextView text{std::get<IndexFor(STRING)>(ctx.extract(_expr))};
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    hdr.url().query_set(text);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_query::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                      YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (expr.is_null()) {
    expr = Feature{FeatureView::Literal(""_tv)};
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark());
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the query for the proxy request.
 */
class Do_proxy_req_query : public Directive
{
  using super_type = Directive;          ///< Parent type.
  using self_type  = Do_proxy_req_query; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  explicit Do_proxy_req_query(Expr &&fmt);

  Errata invoke(Context &ctx) override;

  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _fmt; ///< Host feature.
};

const std::string Do_proxy_req_query::KEY{"proxy-req-query"};
const HookMask    Do_proxy_req_query::HOOKS{MaskFor({Hook::PREQ})};

Do_proxy_req_query::Do_proxy_req_query(Expr &&fmt) : _fmt(std::move(fmt)) {}

Errata
Do_proxy_req_query::invoke(Context &ctx)
{
  TextView text{std::get<IndexFor(STRING)>(ctx.extract(_fmt))};
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    hdr.url().query_set(text);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_query::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                         YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark());
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
class QueryValueDirective : public Directive
{
  using self_type  = QueryValueDirective;
  using super_type = Directive;

public:
  TextView _name; ///< Query value key name.
  Expr     _expr; ///< Replacement value.

  /** Base constructor.
   *
   * @param name Name of the field.
   * @param expr Value to assign to the field.
   */
  QueryValueDirective(TextView const &name, Expr &&expr);

  /** Load from configuration.
   *
   * @param cfg Configuration.
   * @param maker Subclass maker.
   * @param key Name of the key identifying the directive.
   * @param name Field name (directive argumnet).
   * @param key_value  Value of the node for @a key.
   * @return An instance of the directive for @a key, or errors.
   */
  static Rv<Handle> load(Config &cfg, std::function<Handle(TextView const &name, Expr &&fmt)> const &maker, TextView const &key,
                         TextView const &arg, YAML::Node key_value);

  /** Invoke the directive on a URL.
   *
   * @param ctx Runtime context.
   * @param url URL.
   * @return Errors, if any.
   */
  Errata invoke_on_url(Context &ctx, ts::URL &&url);

protected:
  /** Get the directive name (key).
   *
   * @return The directive key.
   *
   * Used by subclasses to provide the key for diagnostics.
   */
  virtual swoc::TextView key() const = 0;
};

QueryValueDirective::QueryValueDirective(TextView const &name, Expr &&expr) : _name(name), _expr(std::move(expr)) {}

auto
QueryValueDirective::load(Config &cfg, std::function<Handle(const TextView &, Expr &&)> const &maker, TextView const &key,
                          TextView const &arg, YAML::Node key_value) -> Rv<Handle>
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing value for "{}".)", key);
    return std::move(errata);
  }

  auto expr_type = expr.result_type();
  if (!expr_type.has_value()) {
    return Errata(S_ERROR, R"(Directive "{}" must have a value.)", key);
  }
  return maker(cfg.localize(arg), std::move(expr));
}

Errata
QueryValueDirective::invoke_on_url(Context &ctx, ts::URL &&url)
{
  if (url.is_valid()) {
    auto qs = query_value_update(ctx, url.query(), _name, ctx.extract(_expr), true, false);
    url.query_set(qs);
    ctx.transient_discard();
  }
  return Errata(S_ERROR, "Failed to update query value {} because the URL could not be found.", _name);
}

// --
class Do_ua_req_query_value : public QueryValueDirective
{
  using self_type  = Do_ua_req_query_value;
  using super_type = QueryValueDirective;

public:
  static inline const std::string KEY{"ua-req-query-value"}; ///< Directive key.
  static inline const HookMask    HOOKS{
    MaskFor(Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP)}; ///< Valid hooks for directive.

  using super_type::invoke;
  Errata invoke(Context &ctx) override;

  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  using super_type::super_type; // Inherit super_type constructors.
  TextView key() const override;
};

TextView
Do_ua_req_query_value::key() const
{
  return KEY;
}

Errata
Do_ua_req_query_value::invoke(Context &ctx)
{
  return this->invoke_on_url(ctx, ctx.ua_req_hdr().url());
}

Rv<Directive::Handle>
Do_ua_req_query_value::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg,
                            YAML::Node key_value)
{
  return super_type::load(
    cfg, [](TextView const &name, Expr &&fmt) -> Handle { return Handle(new self_type(name, std::move(fmt))); }, KEY, arg,
    key_value);
}

// --
class Do_proxy_req_query_value : public QueryValueDirective
{
  using self_type  = Do_proxy_req_query_value;
  using super_type = QueryValueDirective;

public:
  static inline const std::string KEY{"proxy-req-query-value"}; ///< Directive key.
  static inline const HookMask    HOOKS{MaskFor(Hook::PREQ)};   ///< Valid hooks for directive.

  using super_type::invoke;
  Errata invoke(Context &ctx) override;

  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  using super_type::super_type; // Inherit super_type constructors.
  TextView key() const override;
};

TextView
Do_proxy_req_query_value::key() const
{
  return KEY;
}

Errata
Do_proxy_req_query_value::invoke(Context &ctx)
{
  return this->invoke_on_url(ctx, ctx.proxy_req_hdr().url());
}

Rv<Directive::Handle>
Do_proxy_req_query_value::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg,
                               YAML::Node key_value)
{
  return super_type::load(
    cfg, [](TextView const &name, Expr &&fmt) -> Handle { return Handle(new self_type(name, std::move(fmt))); }, KEY, arg,
    key_value);
}
/* ------------------------------------------------------------------------------------ */
namespace
{
Ex_ua_req_query    ua_req_query;
Ex_proxy_req_query proxy_req_query;
Ex_pre_remap_query pre_remap_query;

Ex_ua_req_query_value    ua_req_query_value;
Ex_pre_remap_query       pre_remap_req_query_value;
Ex_proxy_req_query_value proxy_req_query_value;

[[maybe_unused]] bool INITIALIZED = []() -> bool {
  Extractor::define(Ex_ua_req_query::NAME, &ua_req_query);
  Extractor::define(Ex_pre_remap_query::NAME, &pre_remap_query);
  Extractor::define(Ex_proxy_req_query::NAME, &proxy_req_query);

  Extractor::define(Ex_ua_req_query_value::NAME, &ua_req_query_value);
  Extractor::define(Ex_pre_remap_req_query_value::NAME, &ua_req_query_value);
  Extractor::define(Ex_proxy_req_query_value::NAME, &ua_req_query_value);

  Modifier::define<Mod_query_sort>();
  Modifier::define<Mod_query_filter>();

  Config::define<Do_ua_req_query>();
  Config::define<Do_ua_req_query_value>();
  Config::define<Do_proxy_req_query>();
  Config::define<Do_proxy_req_query_value>();

  return true;
}();
} // namespace
