"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const errors = require("../../../../base/common/errors.js");
const event = require("../../../../base/common/event.js");
const idGenerator = require("../../../../base/common/idGenerator.js");
const lifecycle = require("../../../../base/common/lifecycle.js");
const map = require("../../../../base/common/map.js");
const resources = require("../../../../base/common/resources.js");
const strings = require("../../../../base/common/strings.js");
const range = require("../../../common/core/range.js");
const nls = require("../../../../nls.js");
var __awaiter = globalThis && globalThis.__awaiter || function(thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function(resolve) {
      resolve(value);
    });
  }
  return new (P || (P = Promise))(function(resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }
    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }
    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }
    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};
class OneReference {
  constructor(isProviderFirst, parent, link, _rangeCallback) {
    this.isProviderFirst = isProviderFirst;
    this.parent = parent;
    this.link = link;
    this._rangeCallback = _rangeCallback;
    this.id = idGenerator.defaultGenerator.nextId();
  }
  get uri() {
    return this.link.uri;
  }
  get range() {
    var _a, _b;
    return (_b = (_a = this._range) !== null && _a !== void 0 ? _a : this.link.targetSelectionRange) !== null && _b !== void 0 ? _b : this.link.range;
  }
  set range(value) {
    this._range = value;
    this._rangeCallback(this);
  }
  get ariaMessage() {
    var _a;
    const preview = (_a = this.parent.getPreview(this)) === null || _a === void 0 ? void 0 : _a.preview(this.range);
    if (!preview) {
      return nls.localize("aria.oneReference", "symbol in {0} on line {1} at column {2}", resources.basename(this.uri), this.range.startLineNumber, this.range.startColumn);
    } else {
      return nls.localize({ key: "aria.oneReference.preview", comment: ["Placeholders are: 0: filename, 1:line number, 2: column number, 3: preview snippet of source code"] }, "symbol in {0} on line {1} at column {2}, {3}", resources.basename(this.uri), this.range.startLineNumber, this.range.startColumn, preview.value);
    }
  }
}
class FilePreview {
  constructor(_modelReference) {
    this._modelReference = _modelReference;
  }
  dispose() {
    this._modelReference.dispose();
  }
  preview(range$1, n = 8) {
    const model = this._modelReference.object.textEditorModel;
    if (!model) {
      return void 0;
    }
    const { startLineNumber, startColumn, endLineNumber, endColumn } = range$1;
    const word = model.getWordUntilPosition({ lineNumber: startLineNumber, column: startColumn - n });
    const beforeRange = new range.Range(startLineNumber, word.startColumn, startLineNumber, startColumn);
    const afterRange = new range.Range(
      endLineNumber,
      endColumn,
      endLineNumber,
      1073741824
      /* MAX_SAFE_SMALL_INTEGER */
    );
    const before = model.getValueInRange(beforeRange).replace(/^\s+/, "");
    const inside = model.getValueInRange(range$1);
    const after = model.getValueInRange(afterRange).replace(/\s+$/, "");
    return {
      value: before + inside + after,
      highlight: { start: before.length, end: before.length + inside.length }
    };
  }
}
class FileReferences {
  constructor(parent, uri) {
    this.parent = parent;
    this.uri = uri;
    this.children = [];
    this._previews = new map.ResourceMap();
  }
  dispose() {
    lifecycle.dispose(this._previews.values());
    this._previews.clear();
  }
  getPreview(child) {
    return this._previews.get(child.uri);
  }
  get ariaMessage() {
    const len = this.children.length;
    if (len === 1) {
      return nls.localize("aria.fileReferences.1", "1 symbol in {0}, full path {1}", resources.basename(this.uri), this.uri.fsPath);
    } else {
      return nls.localize("aria.fileReferences.N", "{0} symbols in {1}, full path {2}", len, resources.basename(this.uri), this.uri.fsPath);
    }
  }
  resolve(textModelResolverService) {
    return __awaiter(this, void 0, void 0, function* () {
      if (this._previews.size !== 0) {
        return this;
      }
      for (let child of this.children) {
        if (this._previews.has(child.uri)) {
          continue;
        }
        try {
          const ref = yield textModelResolverService.createModelReference(child.uri);
          this._previews.set(child.uri, new FilePreview(ref));
        } catch (err) {
          errors.onUnexpectedError(err);
        }
      }
      return this;
    });
  }
}
class ReferencesModel {
  constructor(links, title) {
    this.groups = [];
    this.references = [];
    this._onDidChangeReferenceRange = new event.Emitter();
    this.onDidChangeReferenceRange = this._onDidChangeReferenceRange.event;
    this._links = links;
    this._title = title;
    const [providersFirst] = links;
    links.sort(ReferencesModel._compareReferences);
    let current;
    for (let link of links) {
      if (!current || !resources.extUri.isEqual(current.uri, link.uri, true)) {
        current = new FileReferences(this, link.uri);
        this.groups.push(current);
      }
      if (current.children.length === 0 || ReferencesModel._compareReferences(link, current.children[current.children.length - 1]) !== 0) {
        const oneRef = new OneReference(providersFirst === link, current, link, (ref) => this._onDidChangeReferenceRange.fire(ref));
        this.references.push(oneRef);
        current.children.push(oneRef);
      }
    }
  }
  dispose() {
    lifecycle.dispose(this.groups);
    this._onDidChangeReferenceRange.dispose();
    this.groups.length = 0;
  }
  clone() {
    return new ReferencesModel(this._links, this._title);
  }
  get title() {
    return this._title;
  }
  get isEmpty() {
    return this.groups.length === 0;
  }
  get ariaMessage() {
    if (this.isEmpty) {
      return nls.localize("aria.result.0", "No results found");
    } else if (this.references.length === 1) {
      return nls.localize("aria.result.1", "Found 1 symbol in {0}", this.references[0].uri.fsPath);
    } else if (this.groups.length === 1) {
      return nls.localize("aria.result.n1", "Found {0} symbols in {1}", this.references.length, this.groups[0].uri.fsPath);
    } else {
      return nls.localize("aria.result.nm", "Found {0} symbols in {1} files", this.references.length, this.groups.length);
    }
  }
  nextOrPreviousReference(reference, next) {
    let { parent } = reference;
    let idx = parent.children.indexOf(reference);
    let childCount = parent.children.length;
    let groupCount = parent.parent.groups.length;
    if (groupCount === 1 || next && idx + 1 < childCount || !next && idx > 0) {
      if (next) {
        idx = (idx + 1) % childCount;
      } else {
        idx = (idx + childCount - 1) % childCount;
      }
      return parent.children[idx];
    }
    idx = parent.parent.groups.indexOf(parent);
    if (next) {
      idx = (idx + 1) % groupCount;
      return parent.parent.groups[idx].children[0];
    } else {
      idx = (idx + groupCount - 1) % groupCount;
      return parent.parent.groups[idx].children[parent.parent.groups[idx].children.length - 1];
    }
  }
  nearestReference(resource, position) {
    const nearest = this.references.map((ref, idx) => {
      return {
        idx,
        prefixLen: strings.commonPrefixLength(ref.uri.toString(), resource.toString()),
        offsetDist: Math.abs(ref.range.startLineNumber - position.lineNumber) * 100 + Math.abs(ref.range.startColumn - position.column)
      };
    }).sort((a, b) => {
      if (a.prefixLen > b.prefixLen) {
        return -1;
      } else if (a.prefixLen < b.prefixLen) {
        return 1;
      } else if (a.offsetDist < b.offsetDist) {
        return -1;
      } else if (a.offsetDist > b.offsetDist) {
        return 1;
      } else {
        return 0;
      }
    })[0];
    if (nearest) {
      return this.references[nearest.idx];
    }
    return void 0;
  }
  referenceAt(resource, position) {
    for (const ref of this.references) {
      if (ref.uri.toString() === resource.toString()) {
        if (range.Range.containsPosition(ref.range, position)) {
          return ref;
        }
      }
    }
    return void 0;
  }
  firstReference() {
    for (const ref of this.references) {
      if (ref.isProviderFirst) {
        return ref;
      }
    }
    return this.references[0];
  }
  static _compareReferences(a, b) {
    return resources.extUri.compare(a.uri, b.uri) || range.Range.compareRangesUsingStarts(a.range, b.range);
  }
}
exports.FilePreview = FilePreview;
exports.FileReferences = FileReferences;
exports.OneReference = OneReference;
exports.ReferencesModel = ReferencesModel;
