text: import { Range } from "./range";
  import * as p from "../../core/properties";
  import { map } from "../../core/util/arrayable";
  import { every, sum } from "../../core/util/array";
  import { isArray, isNumber, isString } from "../../core/util/types";
  import { unreachable } from "../../core/util/assert";
  export function map_one_level(factors, padding, offset = 0) {
      const mapping = new Map();
      for (let i = 0; i < factors.length; i++) {
          const factor = factors[i];
          if (!mapping.has(factor))
              mapping.set(factor, { value: 0.5 + i * (1 + padding) + offset });
          else
              throw new Error(`duplicate factor or subfactor: ${factor}`);
      }
      return [mapping, (factors.length - 1) * padding];
  }
  export function map_two_levels(factors, outer_pad, factor_pad, offset = 0) {
      var _a;
      const mapping = new Map();
      const tops = new Map();
      for (const [f0, f1] of factors) {
          const top = (_a = tops.get(f0)) !== null && _a !== void 0 ? _a : [];
          tops.set(f0, [...top, f1]);
      }
      let suboffset = offset;
      let total_subpad = 0;
      for (const [f0, top] of tops) {
          const n = top.length;
          const [submap, subpad] = map_one_level(top, factor_pad, suboffset);
          total_subpad += subpad;
          const subtot = sum(top.map((f1) => submap.get(f1).value));
          mapping.set(f0, { value: subtot / n, mapping: submap });
          suboffset += n + outer_pad + subpad;
      }
      return [mapping, (tops.size - 1) * outer_pad + total_subpad];
  }
  export function map_three_levels(factors, outer_pad, inner_pad, factor_pad, offset = 0) {
      var _a;
      const mapping = new Map();
      const tops = new Map();
      for (const [f0, f1, f2] of factors) {
          const top = (_a = tops.get(f0)) !== null && _a !== void 0 ? _a : [];
          tops.set(f0, [...top, [f1, f2]]);
      }
      let suboffset = offset;
      let total_subpad = 0;
      for (const [f0, top] of tops) {
          const n = top.length;
          const [submap, subpad] = map_two_levels(top, inner_pad, factor_pad, suboffset);
          total_subpad += subpad;
          const subtot = sum(top.map(([f1]) => submap.get(f1).value));
          mapping.set(f0, { value: subtot / n, mapping: submap });
          suboffset += n + outer_pad + subpad;
      }
      return [mapping, (tops.size - 1) * outer_pad + total_subpad];
  }
  export class FactorRange extends Range {
      constructor(attrs) {
          super(attrs);
      }
      static init_FactorRange() {
          this.define({
              factors: [p.Array, []],
              factor_padding: [p.Number, 0],
              subgroup_padding: [p.Number, 0.8],
              group_padding: [p.Number, 1.4],
              range_padding: [p.Number, 0],
              range_padding_units: [p.PaddingUnits, "percent"],
              start: [p.Number],
              end: [p.Number],
          });
          this.internal({
              levels: [p.Number],
              mids: [p.Array, null],
              tops: [p.Array, null],
          });
      }
      get min() {
          return this.start;
      }
      get max() {
          return this.end;
      }
      initialize() {
          super.initialize();
          this._init(true);
      }
      connect_signals() {
          super.connect_signals();
          this.connect(this.properties.factors.change, () => this.reset());
          this.connect(this.properties.factor_padding.change, () => this.reset());
          this.connect(this.properties.group_padding.change, () => this.reset());
          this.connect(this.properties.subgroup_padding.change, () => this.reset());
          this.connect(this.properties.range_padding.change, () => this.reset());
          this.connect(this.properties.range_padding_units.change, () => this.reset());
      }
      reset() {
          this._init(false);
          this.change.emit();
      }
      _lookup(x) {
          switch (x.length) {
              case 1: {
                  const [f0] = x;
                  const mapping = this._mapping;
                  const y0 = mapping.get(f0);
                  return y0 != null ? y0.value : NaN;
              }
              case 2: {
                  const [f0, f1] = x;
                  const mapping = this._mapping;
                  const y0 = mapping.get(f0);
                  if (y0 != null) {
                      const y1 = y0.mapping.get(f1);
                      if (y1 != null)
                          return y1.value;
                  }
                  return NaN;
              }
              case 3: {
                  const [f0, f1, f2] = x;
                  const mapping = this._mapping;
                  const y0 = mapping.get(f0);
                  if (y0 != null) {
                      const y1 = y0.mapping.get(f1);
                      if (y1 != null) {
                          const y2 = y1.mapping.get(f2);
                          if (y2 != null)
                              return y2.value;
                      }
                  }
                  return NaN;
              }
              default:
                  unreachable();
          }
      }
      // convert a string factor into a synthetic coordinate
      synthetic(x) {
          if (isNumber(x))
              return x;
          if (isString(x))
              return this._lookup([x]);
          let offset = 0;
          const off = x[x.length - 1];
          if (isNumber(off)) {
              offset = off;
              x = x.slice(0, -1);
          }
          return this._lookup(x) + offset;
      }
      // convert an array of string factors into synthetic coordinates
      v_synthetic(xs) {
          return map(xs, (x) => this.synthetic(x));
      }
      _init(silent) {
          const { levels, mapping, tops, mids, inside_padding } = (() => {
              if (every(this.factors, isString)) {
                  const factors = this.factors;
                  const [mapping, inside_padding] = map_one_level(factors, this.factor_padding);
                  const tops = null;
                  const mids = null;
                  return { levels: 1, mapping, tops, mids, inside_padding };
              }
              else if (every(this.factors, (x) => isArray(x) && x.length == 2 && isString(x[0]) && isString(x[1]))) {
                  const factors = this.factors;
                  const [mapping, inside_padding] = map_two_levels(factors, this.group_padding, this.factor_padding);
                  const tops = [...mapping.keys()];
                  const mids = null;
                  return { levels: 2, mapping, tops, mids, inside_padding };
              }
              else if (every(this.factors, (x) => isArray(x) && x.length == 3 && isString(x[0]) && isString(x[1]) && isString(x[2]))) {
                  const factors = this.factors;
                  const [mapping, inside_padding] = map_three_levels(factors, this.group_padding, this.subgroup_padding, this.factor_padding);
                  const tops = [...mapping.keys()];
                  const mids = [];
                  for (const [f0, L2] of mapping) {
                      for (const f1 of L2.mapping.keys()) {
                          mids.push([f0, f1]);
                      }
                  }
                  return { levels: 3, mapping, tops, mids, inside_padding };
              }
              else
                  unreachable();
          })();
          this._mapping = mapping;
          this.tops = tops;
          this.mids = mids;
          let start = 0;
          let end = this.factors.length + inside_padding;
          if (this.range_padding_units == "percent") {
              const half_span = (end - start) * this.range_padding / 2;
              start -= half_span;
              end += half_span;
          }
          else {
              start -= this.range_padding;
              end += this.range_padding;
          }
          this.setv({ start, end, levels }, { silent });
          if (this.bounds == "auto")
              this.setv({ bounds: [start, end] }, { silent: true });
      }
  }
  FactorRange.__name__ = "FactorRange";
  FactorRange.init_FactorRange();
  //# sourceMappingURL=factor_range.js.map