// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import type * as Platform from '../../core/platform/platform.js';
import type {ContentDataOrError} from '../text_utils/ContentData.js';
import type {SearchMatch} from '../text_utils/ContentProvider.js';
import * as Workspace from '../workspace/workspace.js';

import {
  type AutomaticFileSystem,
  type AutomaticFileSystemManager,
  Events as AutomaticFileSystemManagerEvents
} from './AutomaticFileSystemManager.js';
import {
  Events as IsolatedFileSystemManagerEvents,
  type IsolatedFileSystemManager
} from './IsolatedFileSystemManager.js';

/**
 * Placeholder project that acts as an empty file system within the workspace,
 * and automatically disappears when the user connects the automatic workspace
 * folder.
 *
 * @see AutomaticFileSystemWorkspaceBinding
 */
export class FileSystem implements Workspace.Workspace.Project {
  readonly automaticFileSystem: Readonly<AutomaticFileSystem>;
  readonly automaticFileSystemManager: AutomaticFileSystemManager;
  readonly #workspace: Workspace.Workspace.WorkspaceImpl;

  constructor(
      automaticFileSystem: Readonly<AutomaticFileSystem>, automaticFileSystemManager: AutomaticFileSystemManager,
      workspace: Workspace.Workspace.WorkspaceImpl) {
    this.automaticFileSystem = automaticFileSystem;
    this.automaticFileSystemManager = automaticFileSystemManager;
    this.#workspace = workspace;
  }

  workspace(): Workspace.Workspace.WorkspaceImpl {
    return this.#workspace;
  }

  id(): string {
    return `${this.type()}:${this.automaticFileSystem.root}:${this.automaticFileSystem.uuid}`;
  }

  type(): Workspace.Workspace.projectTypes {
    return Workspace.Workspace.projectTypes.ConnectableFileSystem;
  }

  isServiceProject(): boolean {
    return false;
  }

  displayName(): string {
    const {root} = this.automaticFileSystem;
    let slash = root.lastIndexOf('/');
    if (slash === -1 && Host.Platform.isWin()) {
      slash = root.lastIndexOf('\\');
    }
    return root.substr(slash + 1);
  }

  async requestMetadata(_uiSourceCode: Workspace.UISourceCode.UISourceCode):
      Promise<Workspace.UISourceCode.UISourceCodeMetadata|null> {
    throw new Error('Not implemented');
  }

  async requestFileContent(_uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<ContentDataOrError> {
    throw new Error('Not implemented');
  }

  canSetFileContent(): boolean {
    return false;
  }

  async setFileContent(_uiSourceCode: Workspace.UISourceCode.UISourceCode, _newContent: string, _isBase64: boolean):
      Promise<void> {
    throw new Error('Not implemented');
  }

  fullDisplayName(_uiSourceCode: Workspace.UISourceCode.UISourceCode): string {
    throw new Error('Not implemented');
  }

  mimeType(_uiSourceCode: Workspace.UISourceCode.UISourceCode): string {
    throw new Error('Not implemented');
  }

  canRename(): boolean {
    return false;
  }

  rename(
      _uiSourceCode: Workspace.UISourceCode.UISourceCode, _newName: Platform.DevToolsPath.RawPathString,
      _callback:
          (arg0: boolean, arg1?: string, arg2?: Platform.DevToolsPath.UrlString,
           arg3?: Common.ResourceType.ResourceType) => void): void {
    throw new Error('Not implemented');
  }

  excludeFolder(_path: Platform.DevToolsPath.UrlString): void {
    throw new Error('Not implemented');
  }

  canExcludeFolder(_path: Platform.DevToolsPath.EncodedPathString): boolean {
    return false;
  }

  async createFile(
      _path: Platform.DevToolsPath.EncodedPathString, _name: string|null, _content: string,
      _isBase64?: boolean): Promise<Workspace.UISourceCode.UISourceCode|null> {
    throw new Error('Not implemented');
  }

  canCreateFile(): boolean {
    return false;
  }

  deleteFile(_uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
    throw new Error('Not implemented');
  }

  async deleteDirectoryRecursively(_path: Platform.DevToolsPath.EncodedPathString): Promise<boolean> {
    throw new Error('Not implemented');
  }

  remove(): void {
  }

  removeUISourceCode(_url: Platform.DevToolsPath.UrlString): void {
    throw new Error('Not implemented');
  }

  async searchInFileContent(
      _uiSourceCode: Workspace.UISourceCode.UISourceCode, _query: string, _caseSensitive: boolean,
      _isRegex: boolean): Promise<SearchMatch[]> {
    return [];
  }

  async findFilesMatchingSearchRequest(
      _searchConfig: Workspace.SearchConfig.SearchConfig,
      _filesMatchingFileQuery: Workspace.UISourceCode.UISourceCode[],
      _progress: Common.Progress.Progress): Promise<Map<Workspace.UISourceCode.UISourceCode, SearchMatch[]|null>> {
    return new Map();
  }

  indexContent(_progress: Common.Progress.Progress): void {
  }

  uiSourceCodeForURL(_url: Platform.DevToolsPath.UrlString): Workspace.UISourceCode.UISourceCode|null {
    return null;
  }

  uiSourceCodes(): Iterable<Workspace.UISourceCode.UISourceCode> {
    return [];
  }
}

let automaticFileSystemWorkspaceBindingInstance: AutomaticFileSystemWorkspaceBinding|undefined;

/**
 * Provides a transient workspace `Project` that doesn't contain any `UISourceCode`s,
 * and only acts as a placeholder for the automatic file system, while it's not
 * connected yet. The placeholder project automatically disappears as soon as
 * the automatic file system is connected successfully.
 */
export class AutomaticFileSystemWorkspaceBinding {
  readonly #automaticFileSystemManager: AutomaticFileSystemManager;
  #fileSystem: FileSystem|null = null;
  readonly #isolatedFileSystemManager: IsolatedFileSystemManager;
  readonly #workspace: Workspace.Workspace.WorkspaceImpl;

  /**
   * @internal
   */
  private constructor(
      automaticFileSystemManager: AutomaticFileSystemManager,
      isolatedFileSystemManager: IsolatedFileSystemManager,
      workspace: Workspace.Workspace.WorkspaceImpl,
  ) {
    this.#automaticFileSystemManager = automaticFileSystemManager;
    this.#isolatedFileSystemManager = isolatedFileSystemManager;
    this.#workspace = workspace;
    this.#automaticFileSystemManager.addEventListener(
        AutomaticFileSystemManagerEvents.AUTOMATIC_FILE_SYSTEM_CHANGED, this.#update, this);
    this.#isolatedFileSystemManager.addEventListener(
        IsolatedFileSystemManagerEvents.FileSystemAdded, this.#update, this);
    this.#isolatedFileSystemManager.addEventListener(
        IsolatedFileSystemManagerEvents.FileSystemRemoved, this.#update, this);
    this.#update();
  }

  /**
   * Yields the `AutomaticFileSystemWorkspaceBinding` singleton.
   *
   * @returns the singleton.
   */
  static instance({forceNew, automaticFileSystemManager, isolatedFileSystemManager, workspace}: {
    forceNew: boolean|null,
    automaticFileSystemManager: AutomaticFileSystemManager|null,
    isolatedFileSystemManager: IsolatedFileSystemManager|null,
    workspace: Workspace.Workspace.WorkspaceImpl|null,
  } = {
    forceNew: false,
    automaticFileSystemManager: null,
    isolatedFileSystemManager: null,
    workspace: null,
  }): AutomaticFileSystemWorkspaceBinding {
    if (!automaticFileSystemWorkspaceBindingInstance || forceNew) {
      if (!automaticFileSystemManager || !isolatedFileSystemManager || !workspace) {
        throw new Error(
            'Unable to create AutomaticFileSystemWorkspaceBinding: ' +
            'automaticFileSystemManager, isolatedFileSystemManager, ' +
            'and workspace must be provided');
      }
      automaticFileSystemWorkspaceBindingInstance = new AutomaticFileSystemWorkspaceBinding(
          automaticFileSystemManager,
          isolatedFileSystemManager,
          workspace,
      );
    }
    return automaticFileSystemWorkspaceBindingInstance;
  }

  /**
   * Clears the `AutomaticFileSystemWorkspaceBinding` singleton (if any);
   */
  static removeInstance(): void {
    if (automaticFileSystemWorkspaceBindingInstance) {
      automaticFileSystemWorkspaceBindingInstance.#dispose();
      automaticFileSystemWorkspaceBindingInstance = undefined;
    }
  }

  #dispose(): void {
    if (this.#fileSystem) {
      this.#workspace.removeProject(this.#fileSystem);
    }
    this.#automaticFileSystemManager.removeEventListener(
        AutomaticFileSystemManagerEvents.AUTOMATIC_FILE_SYSTEM_CHANGED, this.#update, this);
    this.#isolatedFileSystemManager.removeEventListener(
        IsolatedFileSystemManagerEvents.FileSystemAdded, this.#update, this);
    this.#isolatedFileSystemManager.removeEventListener(
        IsolatedFileSystemManagerEvents.FileSystemRemoved, this.#update, this);
  }

  #update(): void {
    const automaticFileSystem = this.#automaticFileSystemManager.automaticFileSystem;
    if (this.#fileSystem !== null) {
      if (this.#fileSystem.automaticFileSystem === automaticFileSystem) {
        return;
      }
      this.#workspace.removeProject(this.#fileSystem);
      this.#fileSystem = null;
    }
    if (automaticFileSystem !== null && automaticFileSystem.state !== 'connected') {
      // Check if we already have a (manually added) file system, and if so, don't
      // offer the option to connect the automatic file system.
      const fileSystemURL = Common.ParsedURL.ParsedURL.rawPathToUrlString(automaticFileSystem.root);
      if (this.#isolatedFileSystemManager.fileSystem(fileSystemURL) === null) {
        this.#fileSystem = new FileSystem(
            automaticFileSystem,
            this.#automaticFileSystemManager,
            this.#workspace,
        );
        this.#workspace.addProject(this.#fileSystem);
      }
    }
  }
}
