import * as Common from "../../core/common/common.js";
import * as Host from "../../core/host/host.js";
import * as Platform from "../../core/platform/platform.js";
import * as SDK from "../../core/sdk/sdk.js";
import * as Bindings from "../bindings/bindings.js";
import * as Workspace from "../workspace/workspace.js";
import { FileSystemWorkspaceBinding } from "./FileSystemWorkspaceBinding.js";
import { PathEncoder, PersistenceImpl } from "./PersistenceImpl.js";
export class Automapping {
  workspace;
  onStatusAdded;
  onStatusRemoved;
  statuses;
  fileSystemUISourceCodes;
  sweepThrottler;
  sourceCodeToProcessingPromiseMap;
  sourceCodeToAutoMappingStatusMap;
  sourceCodeToMetadataMap;
  filesIndex;
  projectFoldersIndex;
  activeFoldersIndex;
  interceptors;
  constructor(workspace, onStatusAdded, onStatusRemoved) {
    this.workspace = workspace;
    this.onStatusAdded = onStatusAdded;
    this.onStatusRemoved = onStatusRemoved;
    this.statuses = /* @__PURE__ */ new Set();
    this.fileSystemUISourceCodes = new FileSystemUISourceCodes();
    this.sweepThrottler = new Common.Throttler.Throttler(100);
    this.sourceCodeToProcessingPromiseMap = /* @__PURE__ */ new WeakMap();
    this.sourceCodeToAutoMappingStatusMap = /* @__PURE__ */ new WeakMap();
    this.sourceCodeToMetadataMap = /* @__PURE__ */ new WeakMap();
    const pathEncoder = new PathEncoder();
    this.filesIndex = new FilePathIndex(pathEncoder);
    this.projectFoldersIndex = new FolderIndex(pathEncoder);
    this.activeFoldersIndex = new FolderIndex(pathEncoder);
    this.interceptors = [];
    this.workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded, (event) => this.onUISourceCodeAdded(event.data));
    this.workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRemoved, (event) => this.onUISourceCodeRemoved(event.data));
    this.workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRenamed, this.onUISourceCodeRenamed, this);
    this.workspace.addEventListener(Workspace.Workspace.Events.ProjectAdded, (event) => this.onProjectAdded(event.data), this);
    this.workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, (event) => this.onProjectRemoved(event.data), this);
    for (const fileSystem of workspace.projects()) {
      this.onProjectAdded(fileSystem);
    }
    for (const uiSourceCode of workspace.uiSourceCodes()) {
      this.onUISourceCodeAdded(uiSourceCode);
    }
  }
  addNetworkInterceptor(interceptor) {
    this.interceptors.push(interceptor);
    this.scheduleRemap();
  }
  scheduleRemap() {
    for (const status of this.statuses.values()) {
      this.clearNetworkStatus(status.network);
    }
    this.scheduleSweep();
  }
  scheduleSweep() {
    void this.sweepThrottler.schedule(sweepUnmapped.bind(this));
    function sweepUnmapped() {
      const networkProjects = this.workspace.projectsForType(Workspace.Workspace.projectTypes.Network);
      for (const networkProject of networkProjects) {
        for (const uiSourceCode of networkProject.uiSourceCodes()) {
          this.computeNetworkStatus(uiSourceCode);
        }
      }
      this.onSweepHappenedForTest();
      return Promise.resolve();
    }
  }
  onSweepHappenedForTest() {
  }
  onProjectRemoved(project) {
    for (const uiSourceCode of project.uiSourceCodes()) {
      this.onUISourceCodeRemoved(uiSourceCode);
    }
    if (project.type() !== Workspace.Workspace.projectTypes.FileSystem) {
      return;
    }
    const fileSystem = project;
    for (const gitFolder of fileSystem.initialGitFolders()) {
      this.projectFoldersIndex.removeFolder(gitFolder);
    }
    this.projectFoldersIndex.removeFolder(fileSystem.fileSystemPath());
    this.scheduleRemap();
  }
  onProjectAdded(project) {
    if (project.type() !== Workspace.Workspace.projectTypes.FileSystem) {
      return;
    }
    const fileSystem = project;
    for (const gitFolder of fileSystem.initialGitFolders()) {
      this.projectFoldersIndex.addFolder(gitFolder);
    }
    this.projectFoldersIndex.addFolder(fileSystem.fileSystemPath());
    project.uiSourceCodes().forEach(this.onUISourceCodeAdded.bind(this));
    this.scheduleRemap();
  }
  onUISourceCodeAdded(uiSourceCode) {
    const project = uiSourceCode.project();
    if (project.type() === Workspace.Workspace.projectTypes.FileSystem) {
      if (!FileSystemWorkspaceBinding.fileSystemSupportsAutomapping(project)) {
        return;
      }
      this.filesIndex.addPath(uiSourceCode.url());
      this.fileSystemUISourceCodes.add(uiSourceCode);
      this.scheduleSweep();
    } else if (project.type() === Workspace.Workspace.projectTypes.Network) {
      this.computeNetworkStatus(uiSourceCode);
    }
  }
  onUISourceCodeRemoved(uiSourceCode) {
    if (uiSourceCode.project().type() === Workspace.Workspace.projectTypes.FileSystem) {
      this.filesIndex.removePath(uiSourceCode.url());
      this.fileSystemUISourceCodes.delete(uiSourceCode.url());
      const status = this.sourceCodeToAutoMappingStatusMap.get(uiSourceCode);
      if (status) {
        this.clearNetworkStatus(status.network);
      }
    } else if (uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Network) {
      this.clearNetworkStatus(uiSourceCode);
    }
  }
  onUISourceCodeRenamed(event) {
    const { uiSourceCode, oldURL } = event.data;
    if (uiSourceCode.project().type() !== Workspace.Workspace.projectTypes.FileSystem) {
      return;
    }
    this.filesIndex.removePath(oldURL);
    this.fileSystemUISourceCodes.delete(oldURL);
    const status = this.sourceCodeToAutoMappingStatusMap.get(uiSourceCode);
    if (status) {
      this.clearNetworkStatus(status.network);
    }
    this.filesIndex.addPath(uiSourceCode.url());
    this.fileSystemUISourceCodes.add(uiSourceCode);
    this.scheduleSweep();
  }
  computeNetworkStatus(networkSourceCode) {
    if (this.sourceCodeToProcessingPromiseMap.has(networkSourceCode) || this.sourceCodeToAutoMappingStatusMap.has(networkSourceCode)) {
      return;
    }
    if (this.interceptors.some((interceptor) => interceptor(networkSourceCode))) {
      return;
    }
    if (networkSourceCode.url().startsWith("wasm://")) {
      return;
    }
    const createBindingPromise = this.createBinding(networkSourceCode).then(validateStatus.bind(this)).then(onStatus.bind(this));
    this.sourceCodeToProcessingPromiseMap.set(networkSourceCode, createBindingPromise);
    async function validateStatus(status) {
      if (!status) {
        return null;
      }
      if (this.sourceCodeToProcessingPromiseMap.get(networkSourceCode) !== createBindingPromise) {
        return null;
      }
      if (status.network.contentType().isFromSourceMap() || !status.fileSystem.contentType().isTextType()) {
        return status;
      }
      if (status.fileSystem.isDirty() && (status.network.isDirty() || status.network.hasCommits())) {
        return null;
      }
      const [fileSystemContent, networkContent] = await Promise.all([status.fileSystem.requestContent(), status.network.project().requestFileContent(status.network)]);
      if (fileSystemContent.content === null || networkContent === null) {
        return null;
      }
      if (this.sourceCodeToProcessingPromiseMap.get(networkSourceCode) !== createBindingPromise) {
        return null;
      }
      const target = Bindings.NetworkProject.NetworkProject.targetForUISourceCode(status.network);
      let isValid = false;
      const fileContent = fileSystemContent.content;
      if (target && target.type() === SDK.Target.Type.Node) {
        if (networkContent.content) {
          const rewrappedNetworkContent = PersistenceImpl.rewrapNodeJSContent(status.fileSystem, fileContent, networkContent.content);
          isValid = fileContent === rewrappedNetworkContent;
        }
      } else {
        if (networkContent.content) {
          isValid = fileContent.trimRight() === networkContent.content.trimRight();
        }
      }
      if (!isValid) {
        this.prevalidationFailedForTest(status);
        return null;
      }
      return status;
    }
    function onStatus(status) {
      if (this.sourceCodeToProcessingPromiseMap.get(networkSourceCode) !== createBindingPromise) {
        return;
      }
      this.sourceCodeToProcessingPromiseMap.delete(networkSourceCode);
      if (!status) {
        this.onBindingFailedForTest();
        return;
      }
      if (this.sourceCodeToAutoMappingStatusMap.has(status.network) || this.sourceCodeToAutoMappingStatusMap.has(status.fileSystem)) {
        return;
      }
      this.statuses.add(status);
      this.sourceCodeToAutoMappingStatusMap.set(status.network, status);
      this.sourceCodeToAutoMappingStatusMap.set(status.fileSystem, status);
      if (status.exactMatch) {
        const projectFolder = this.projectFoldersIndex.closestParentFolder(status.fileSystem.url());
        const newFolderAdded = projectFolder ? this.activeFoldersIndex.addFolder(projectFolder) : false;
        if (newFolderAdded) {
          this.scheduleSweep();
        }
      }
      void this.onStatusAdded.call(null, status);
    }
  }
  prevalidationFailedForTest(_binding) {
  }
  onBindingFailedForTest() {
  }
  clearNetworkStatus(networkSourceCode) {
    if (this.sourceCodeToProcessingPromiseMap.has(networkSourceCode)) {
      this.sourceCodeToProcessingPromiseMap.delete(networkSourceCode);
      return;
    }
    const status = this.sourceCodeToAutoMappingStatusMap.get(networkSourceCode);
    if (!status) {
      return;
    }
    this.statuses.delete(status);
    this.sourceCodeToAutoMappingStatusMap.delete(status.network);
    this.sourceCodeToAutoMappingStatusMap.delete(status.fileSystem);
    if (status.exactMatch) {
      const projectFolder = this.projectFoldersIndex.closestParentFolder(status.fileSystem.url());
      if (projectFolder) {
        this.activeFoldersIndex.removeFolder(projectFolder);
      }
    }
    void this.onStatusRemoved.call(null, status);
  }
  createBinding(networkSourceCode) {
    const url = networkSourceCode.url();
    if (url.startsWith("file://") || url.startsWith("snippet://")) {
      const fileSourceCode = this.fileSystemUISourceCodes.get(url);
      const status = fileSourceCode ? new AutomappingStatus(networkSourceCode, fileSourceCode, false) : null;
      return Promise.resolve(status);
    }
    let networkPath = Common.ParsedURL.ParsedURL.extractPath(url);
    if (networkPath === null) {
      return Promise.resolve(null);
    }
    if (networkPath.endsWith("/")) {
      networkPath = Common.ParsedURL.ParsedURL.concatenate(networkPath, "index.html");
    }
    const similarFiles = this.filesIndex.similarFiles(networkPath).map((path) => this.fileSystemUISourceCodes.get(path));
    if (!similarFiles.length) {
      return Promise.resolve(null);
    }
    return this.pullMetadatas(similarFiles.concat(networkSourceCode)).then(onMetadatas.bind(this));
    function onMetadatas() {
      const activeFiles = similarFiles.filter((file) => Boolean(file) && Boolean(this.activeFoldersIndex.closestParentFolder(file.url())));
      const networkMetadata = this.sourceCodeToMetadataMap.get(networkSourceCode);
      if (!networkMetadata || !networkMetadata.modificationTime && typeof networkMetadata.contentSize !== "number") {
        if (activeFiles.length !== 1) {
          return null;
        }
        return new AutomappingStatus(networkSourceCode, activeFiles[0], false);
      }
      let exactMatches = this.filterWithMetadata(activeFiles, networkMetadata);
      if (!exactMatches.length) {
        exactMatches = this.filterWithMetadata(similarFiles, networkMetadata);
      }
      if (exactMatches.length !== 1) {
        return null;
      }
      return new AutomappingStatus(networkSourceCode, exactMatches[0], true);
    }
  }
  async pullMetadatas(uiSourceCodes) {
    await Promise.all(uiSourceCodes.map(async (file) => {
      this.sourceCodeToMetadataMap.set(file, await file.requestMetadata());
    }));
  }
  filterWithMetadata(files, networkMetadata) {
    return files.filter((file) => {
      const fileMetadata = this.sourceCodeToMetadataMap.get(file);
      if (!fileMetadata) {
        return false;
      }
      const timeMatches = !networkMetadata.modificationTime || !fileMetadata.modificationTime || Math.abs(networkMetadata.modificationTime.getTime() - fileMetadata.modificationTime.getTime()) < 1e3;
      const contentMatches = !networkMetadata.contentSize || fileMetadata.contentSize === networkMetadata.contentSize;
      return timeMatches && contentMatches;
    });
  }
}
class FilePathIndex {
  encoder;
  reversedIndex;
  constructor(encoder) {
    this.encoder = encoder;
    this.reversedIndex = new Common.Trie.Trie();
  }
  addPath(path) {
    const encodedPath = this.encoder.encode(path);
    this.reversedIndex.add(Platform.StringUtilities.reverse(encodedPath));
  }
  removePath(path) {
    const encodedPath = this.encoder.encode(path);
    this.reversedIndex.remove(Platform.StringUtilities.reverse(encodedPath));
  }
  similarFiles(networkPath) {
    const encodedPath = this.encoder.encode(networkPath);
    const reversedEncodedPath = Platform.StringUtilities.reverse(encodedPath);
    const longestCommonPrefix = this.reversedIndex.longestPrefix(reversedEncodedPath, false);
    if (!longestCommonPrefix) {
      return [];
    }
    return this.reversedIndex.words(longestCommonPrefix).map((encodedPath2) => this.encoder.decode(Platform.StringUtilities.reverse(encodedPath2)));
  }
}
class FolderIndex {
  encoder;
  index;
  folderCount;
  constructor(encoder) {
    this.encoder = encoder;
    this.index = new Common.Trie.Trie();
    this.folderCount = /* @__PURE__ */ new Map();
  }
  addFolder(path) {
    if (path.endsWith("/")) {
      path = Common.ParsedURL.ParsedURL.substring(path, 0, path.length - 1);
    }
    const encodedPath = this.encoder.encode(path);
    this.index.add(encodedPath);
    const count = this.folderCount.get(encodedPath) || 0;
    this.folderCount.set(encodedPath, count + 1);
    return count === 0;
  }
  removeFolder(path) {
    if (path.endsWith("/")) {
      path = Common.ParsedURL.ParsedURL.substring(path, 0, path.length - 1);
    }
    const encodedPath = this.encoder.encode(path);
    const count = this.folderCount.get(encodedPath) || 0;
    if (!count) {
      return false;
    }
    if (count > 1) {
      this.folderCount.set(encodedPath, count - 1);
      return false;
    }
    this.index.remove(encodedPath);
    this.folderCount.delete(encodedPath);
    return true;
  }
  closestParentFolder(path) {
    const encodedPath = this.encoder.encode(path);
    const commonPrefix = this.index.longestPrefix(encodedPath, true);
    return this.encoder.decode(commonPrefix);
  }
}
class FileSystemUISourceCodes {
  sourceCodes;
  constructor() {
    this.sourceCodes = /* @__PURE__ */ new Map();
  }
  getPlatformCanonicalFileUrl(path) {
    return Host.Platform.isWin() ? Common.ParsedURL.ParsedURL.toLowerCase(path) : path;
  }
  add(sourceCode) {
    const fileUrl = this.getPlatformCanonicalFileUrl(sourceCode.url());
    this.sourceCodes.set(fileUrl, sourceCode);
  }
  get(fileUrl) {
    fileUrl = this.getPlatformCanonicalFileUrl(fileUrl);
    return this.sourceCodes.get(fileUrl);
  }
  delete(fileUrl) {
    fileUrl = this.getPlatformCanonicalFileUrl(fileUrl);
    this.sourceCodes.delete(fileUrl);
  }
}
export class AutomappingStatus {
  network;
  fileSystem;
  exactMatch;
  constructor(network, fileSystem, exactMatch) {
    this.network = network;
    this.fileSystem = fileSystem;
    this.exactMatch = exactMatch;
  }
}
//# sourceMappingURL=Automapping.js.map
