import { getIndentUnit } from "../../@codemirror/language/dist-index.js";
import { Facet, combineConfig, RangeSetBuilder } from "../../@codemirror/state/dist-index.js";
import { ViewPlugin, EditorView, Decoration } from "../../@codemirror/view/dist-index.js";
function getVisibleLines(view, state = view.state) {
  const lines = /* @__PURE__ */ new Set();
  for (const { from, to } of view.visibleRanges) {
    let pos = from;
    while (pos <= to) {
      const line = state.doc.lineAt(pos);
      if (!lines.has(line)) {
        lines.add(line);
      }
      pos = line.to + 1;
    }
  }
  return lines;
}
function getCurrentLine(state) {
  const currentPos = state.selection.main.head;
  return state.doc.lineAt(currentPos);
}
function numColumns(str, tabSize) {
  let col = 0;
  loop: for (let i = 0; i < str.length; i++) {
    switch (str[i]) {
      case " ":
      case " ": {
        col += 1;
        continue loop;
      }
      case "	": {
        col += tabSize - col % tabSize;
        continue loop;
      }
      case "\r": {
        continue loop;
      }
      default: {
        break loop;
      }
    }
  }
  return col;
}
const indentationMarkerConfig = /* @__PURE__ */ Facet.define({
  combine(configs) {
    return combineConfig(configs, {
      highlightActiveBlock: true,
      hideFirstIndent: false,
      markerType: "fullScope",
      thickness: 1
    });
  }
});
class IndentationMap {
  /**
   * @param lines - The set of lines to get the indentation map for.
   * @param state - The {@link EditorState} to derive the indentation map from.
   * @param unitWidth - The width of the editor's indent unit.
   * @param markerType - The type of indentation to use (terminate at end of scope vs last line of code in scope)
   */
  constructor(lines, state, unitWidth, markerType) {
    this.lines = lines;
    this.state = state;
    this.map = /* @__PURE__ */ new Map();
    this.unitWidth = unitWidth;
    this.markerType = markerType;
    for (const line of this.lines) {
      this.add(line);
    }
    if (this.state.facet(indentationMarkerConfig).highlightActiveBlock) {
      this.findAndSetActiveLines();
    }
  }
  /**
   * Checks if the indentation map has an entry for the given line.
   *
   * @param line - The {@link Line} or line number to check for.
   */
  has(line) {
    return this.map.has(typeof line === "number" ? line : line.number);
  }
  /**
   * Returns the {@link IndentEntry} for the given line.
   *
   * Note that this function will throw an error if the line does not exist in the map.
   *
   * @param line - The {@link Line} or line number to get the entry for.
   */
  get(line) {
    const entry = this.map.get(typeof line === "number" ? line : line.number);
    if (!entry) {
      throw new Error("Line not found in indentation map");
    }
    return entry;
  }
  /**
   * Sets the {@link IndentEntry} for the given line.
   *
   * @param line - The {@link Line} to set the entry for.
   * @param col - The visual beginning whitespace width of the line.
   * @param level - The indentation level of the line.
   */
  set(line, col, level) {
    const empty = !line.text.trim().length;
    const entry = { line, col, level, empty };
    this.map.set(entry.line.number, entry);
    return entry;
  }
  /**
   * Adds a line to the indentation map.
   *
   * @param line - The {@link Line} to add to the map.
   */
  add(line) {
    if (this.has(line)) {
      return this.get(line);
    }
    if (!line.length || !line.text.trim().length) {
      if (line.number === 1) {
        return this.set(line, 0, 0);
      }
      if (line.number === this.state.doc.lines) {
        const prev2 = this.closestNonEmpty(line, -1);
        return this.set(line, 0, prev2.level);
      }
      const prev = this.closestNonEmpty(line, -1);
      const next = this.closestNonEmpty(line, 1);
      if (prev.level >= next.level && this.markerType !== "codeOnly") {
        return this.set(line, 0, prev.level);
      }
      if (prev.empty && prev.level === 0 && next.level !== 0) {
        return this.set(line, 0, 0);
      }
      if (next.level > prev.level) {
        return this.set(line, 0, prev.level + 1);
      }
      return this.set(line, 0, next.level);
    }
    const col = numColumns(line.text, this.state.tabSize);
    const level = Math.floor(col / this.unitWidth);
    return this.set(line, col, level);
  }
  /**
   * Finds the closest non-empty line, starting from the given line.
   *
   * @param from - The {@link Line} to start from.
   * @param dir - The direction to search in. Either `1` or `-1`.
   */
  closestNonEmpty(from, dir) {
    let lineNo = from.number + dir;
    while (dir === -1 ? lineNo >= 1 : lineNo <= this.state.doc.lines) {
      if (this.has(lineNo)) {
        const entry = this.get(lineNo);
        if (!entry.empty) {
          return entry;
        }
      }
      const line2 = this.state.doc.line(lineNo);
      if (line2.text.trim().length) {
        const col = numColumns(line2.text, this.state.tabSize);
        const level = Math.floor(col / this.unitWidth);
        return this.set(line2, col, level);
      }
      lineNo += dir;
    }
    const line = this.state.doc.line(dir === -1 ? 1 : this.state.doc.lines);
    return this.set(line, 0, 0);
  }
  /**
   * Finds the state's active block (via the current selection) and sets all
   * the active indent level for the lines in the block.
   */
  findAndSetActiveLines() {
    const currentLine = getCurrentLine(this.state);
    if (!this.has(currentLine)) {
      return;
    }
    let current = this.get(currentLine);
    if (this.has(current.line.number + 1)) {
      const next = this.get(current.line.number + 1);
      if (next.level > current.level) {
        current = next;
      }
    }
    if (this.has(current.line.number - 1)) {
      const prev = this.get(current.line.number - 1);
      if (prev.level > current.level) {
        current = prev;
      }
    }
    if (current.level === 0) {
      return;
    }
    current.active = current.level;
    let start;
    let end;
    for (start = current.line.number; start > 1; start--) {
      if (!this.has(start - 1)) {
        continue;
      }
      const prev = this.get(start - 1);
      if (prev.level < current.level) {
        break;
      }
      prev.active = current.level;
    }
    for (end = current.line.number; end < this.state.doc.lines; end++) {
      if (!this.has(end + 1)) {
        continue;
      }
      const next = this.get(end + 1);
      if (next.level < current.level) {
        break;
      }
      next.active = current.level;
    }
  }
}
function indentTheme(colorOptions) {
  const defaultColors = {
    light: "#F0F1F2",
    dark: "#2B3245",
    activeLight: "#E4E5E6",
    activeDark: "#3C445C"
  };
  let colors = defaultColors;
  if (colorOptions) {
    colors = Object.assign(Object.assign({}, defaultColors), colorOptions);
  }
  return EditorView.baseTheme({
    "&light": {
      "--indent-marker-bg-color": colors.light,
      "--indent-marker-active-bg-color": colors.activeLight
    },
    "&dark": {
      "--indent-marker-bg-color": colors.dark,
      "--indent-marker-active-bg-color": colors.activeDark
    },
    ".cm-line": {
      position: "relative"
    },
    // this pseudo-element is used to draw the indent markers,
    // while still allowing the line to have its own background.
    ".cm-indent-markers::before": {
      content: '""',
      position: "absolute",
      top: 0,
      // .cm-line has a padding of 2px 
      // https://github.com/codemirror/view/blob/1c0a0880fc904714339f059658f3ba3a88bb8e6e/src/theme.ts#L85
      left: `2px`,
      right: 0,
      bottom: 0,
      background: "var(--indent-markers)",
      pointerEvents: "none",
      zIndex: "-1"
    }
  });
}
function createGradient(markerCssProperty, thickness, indentWidth, startOffset, columns) {
  const gradient = `repeating-linear-gradient(to right, var(${markerCssProperty}) 0 ${thickness}px, transparent ${thickness}px ${indentWidth}ch)`;
  return `${gradient} ${startOffset * indentWidth}.5ch/calc(${indentWidth * columns}ch - 1px) no-repeat`;
}
function makeBackgroundCSS(entry, indentWidth, hideFirstIndent, thickness, activeThickness) {
  const { level, active } = entry;
  activeThickness = activeThickness !== null && activeThickness !== void 0 ? activeThickness : thickness;
  if (hideFirstIndent && level === 0) {
    return [];
  }
  const startAt = hideFirstIndent ? 1 : 0;
  const backgrounds = [];
  if (active !== void 0) {
    const markersBeforeActive = active - startAt - 1;
    if (markersBeforeActive > 0) {
      backgrounds.push(createGradient("--indent-marker-bg-color", thickness, indentWidth, startAt, markersBeforeActive));
    }
    backgrounds.push(createGradient("--indent-marker-active-bg-color", activeThickness, indentWidth, active - 1, 1));
    if (active !== level) {
      backgrounds.push(createGradient("--indent-marker-bg-color", thickness, indentWidth, active, level - active));
    }
  } else {
    backgrounds.push(createGradient("--indent-marker-bg-color", thickness, indentWidth, startAt, level - startAt));
  }
  return backgrounds.join(",");
}
class IndentMarkersClass {
  constructor(view) {
    this.view = view;
    this.unitWidth = getIndentUnit(view.state);
    this.currentLineNumber = getCurrentLine(view.state).number;
    this.generate(view.state);
  }
  update(update) {
    const unitWidth = getIndentUnit(update.state);
    const unitWidthChanged = unitWidth !== this.unitWidth;
    if (unitWidthChanged) {
      this.unitWidth = unitWidth;
    }
    const lineNumber = getCurrentLine(update.state).number;
    const lineNumberChanged = lineNumber !== this.currentLineNumber;
    this.currentLineNumber = lineNumber;
    const activeBlockUpdateRequired = update.state.facet(indentationMarkerConfig).highlightActiveBlock && lineNumberChanged;
    if (update.docChanged || update.viewportChanged || unitWidthChanged || activeBlockUpdateRequired) {
      this.generate(update.state);
    }
  }
  generate(state) {
    const builder = new RangeSetBuilder();
    const lines = getVisibleLines(this.view, state);
    const { hideFirstIndent, markerType, thickness, activeThickness } = state.facet(indentationMarkerConfig);
    const map = new IndentationMap(lines, state, this.unitWidth, markerType);
    for (const line of lines) {
      const entry = map.get(line.number);
      if (!(entry === null || entry === void 0 ? void 0 : entry.level)) {
        continue;
      }
      const backgrounds = makeBackgroundCSS(entry, this.unitWidth, hideFirstIndent, thickness, activeThickness);
      builder.add(line.from, line.from, Decoration.line({
        class: "cm-indent-markers",
        attributes: {
          style: `--indent-markers: ${backgrounds}`
        }
      }));
    }
    this.decorations = builder.finish();
  }
}
function indentationMarkers(config = {}) {
  return [
    indentationMarkerConfig.of(config),
    indentTheme(config.colors),
    ViewPlugin.fromClass(IndentMarkersClass, {
      decorations: (v) => v.decorations
    })
  ];
}
export {
  indentationMarkers
};
//# sourceMappingURL=dist-index.js.map
