// 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 '../../ui/kit/kit.js';

import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import type * as Platform from '../../core/platform/platform.js';
import * as Root from '../../core/root/root.js';
import * as Geometry from '../../models/geometry/geometry.js';
import * as Persistence from '../../models/persistence/persistence.js';
import * as Workspace from '../../models/workspace/workspace.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as UI from '../../ui/legacy/legacy.js';
import {html, nothing, render} from '../../ui/lit/lit.js';

import selectWorkspaceDialogStyles from './selectWorkspaceDialog.css.js';

/*
* Strings that don't need to be translated at this time.
*/
const UIStringsNotTranslate = {
  /**
   * @description Heading of dialog box which asks user to select a workspace folder.
   */
  selectFolder: 'Select folder',
  /**
   * @description Heading of dialog box which asks user to select a workspace folder for a11y clients.
   */
  selectFolderAccessibleLabel: 'Select a folder to apply changes',
  /**
   * @description Button text for canceling workspace selection.
   */
  cancel: 'Cancel',
  /**
   * @description Button text for confirming the selected workspace folder.
   */
  select: 'Select',
  /**
   * @description Button text for adding a workspace folder.
   */
  addFolder: 'Add folder',
  /**
   * @description Explanation for selecting the correct workspace folder.
   */
  selectProjectRoot:
      'Source code from the selected folder is sent to Google. This data may be seen by human reviewers to improve this feature.',
  /**
   * @description Explanation for selecting the correct workspace folder when enterprise logging is off.
   */
  selectProjectRootNoLogging:
      'Source code from the selected folder is sent to Google. This data will not be used to improve Google’s AI models. Your organization may change these settings at any time.',
} as const;

const lockedString = i18n.i18n.lockedString;

interface Folder {
  name: string;
  path: string;
  project?: Workspace.Workspace.Project;
  automaticFileSystem?: Persistence.AutomaticFileSystemManager.AutomaticFileSystem;
}

interface ViewInput {
  folders: Folder[];
  selectedIndex: number;
  selectProjectRootText: Platform.UIString.LocalizedString;
  showAutomaticWorkspaceNudge: boolean;
  onProjectSelected: (index: number) => void;
  onSelectButtonClick: () => void;
  onCancelButtonClick: () => void;
  onAddFolderButtonClick: () => void;
  onListItemKeyDown: (event: KeyboardEvent) => void;
}

type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;

export const SELECT_WORKSPACE_DIALOG_DEFAULT_VIEW: View = (input, _output, target): void => {
  const hasFolders = input.folders.length > 0;
  // clang-format off
  render(
    html`
      <style>${selectWorkspaceDialogStyles}</style>
      <h2 class="dialog-header">${lockedString(UIStringsNotTranslate.selectFolder)}</h2>
      <div class="main-content">
        <div class="select-project-root">${input.selectProjectRootText}</div>
        ${input.showAutomaticWorkspaceNudge ? html`
          <!-- Hardcoding, because there is no 'getFormatLocalizedString' equivalent for 'lockedString' -->
          <div>
            Tip: provide a
            <devtools-link
              class="devtools-link"
              href="https://goo.gle/devtools-automatic-workspace-folders"
              jslogcontext="automatic-workspaces-documentation"
            >com.chrome.devtools.json</devtools-link>
            file to automatically connect your project to DevTools.
          </div>
        ` : nothing}
      </div>
      ${hasFolders ? html`
        <ul role="listbox" aria-label=${lockedString(UIStringsNotTranslate.selectFolder)}
          aria-activedescendant=${input.folders.length > 0 ? `option-${input.selectedIndex}` : ''}>
          ${input.folders.map((folder, index) => {
            const optionId = `option-${index}`;
            return html`
              <li
                id=${optionId}
                @mousedown=${() => input.onProjectSelected(index)}
                @keydown=${input.onListItemKeyDown}
                class=${index === input.selectedIndex ? 'selected' : ''}
                aria-selected=${index === input.selectedIndex ? 'true' : 'false'}
                title=${folder.path}
                role="option"
                tabindex=${index === input.selectedIndex ? '0' : '-1'}
              >
                <devtools-icon class="folder-icon" name="folder"></devtools-icon>
                <span class="ellipsis">${folder.name}</span>
              </li>`;
          })}
        </ul>
      ` : nothing}
      <div class="buttons">
        <devtools-button
          title=${lockedString(UIStringsNotTranslate.cancel)}
          aria-label="Cancel"
          .jslogContext=${'cancel'}
          @click=${input.onCancelButtonClick}
          .variant=${Buttons.Button.Variant.OUTLINED}>${lockedString(UIStringsNotTranslate.cancel)}</devtools-button>
        <devtools-button
          class="add-folder-button"
          title=${lockedString(UIStringsNotTranslate.addFolder)}
          aria-label="Add folder"
          .iconName=${'plus'}
          .jslogContext=${'add-folder'}
          @click=${input.onAddFolderButtonClick}
          .variant=${hasFolders ? Buttons.Button.Variant.TONAL : Buttons.Button.Variant.PRIMARY}>${lockedString(UIStringsNotTranslate.addFolder)}</devtools-button>
        ${hasFolders ? html`
          <devtools-button
            title=${lockedString(UIStringsNotTranslate.select)}
            aria-label="Select"
            @click=${input.onSelectButtonClick}
            .jslogContext=${'select'}
            .variant=${Buttons.Button.Variant.PRIMARY}>${lockedString(UIStringsNotTranslate.select)}</devtools-button>
        ` : nothing}
      </div>
    `,
    target
  );
  // clang-format on
};

export class SelectWorkspaceDialog extends UI.Widget.VBox {
  #view: View;
  #workspace = Workspace.Workspace.WorkspaceImpl.instance();
  #selectedIndex = 0;
  #onProjectSelected: (project: Workspace.Workspace.Project) => void;
  #dialog: UI.Dialog.Dialog;
  #automaticFileSystemManager = Persistence.AutomaticFileSystemManager.AutomaticFileSystemManager.instance();
  #folders: Folder[] = [];

  constructor(
      options: {
        dialog: UI.Dialog.Dialog,
        onProjectSelected: (project: Workspace.Workspace.Project) => void,
        currentProject?: Workspace.Workspace.Project,
      },
      view?: View) {
    super();
    this.#onProjectSelected = options.onProjectSelected;
    this.#dialog = options.dialog;
    this.#updateProjectsAndFolders();

    if (options.currentProject) {
      this.#selectedIndex = Math.max(0, this.#folders.findIndex(folder => folder.project === options.currentProject));
    }

    this.#view = view ?? SELECT_WORKSPACE_DIALOG_DEFAULT_VIEW;
    this.requestUpdate();
    void this.updateComplete.then(() => {
      this.contentElement?.querySelector<HTMLUListElement>('.selected')?.focus();
    });
  }

  override wasShown(): void {
    super.wasShown();
    this.#workspace.addEventListener(Workspace.Workspace.Events.ProjectAdded, this.#onProjectAdded, this);
    this.#workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, this.#onProjectRemoved, this);
  }

  override willHide(): void {
    super.willHide();
    this.#workspace.removeEventListener(Workspace.Workspace.Events.ProjectAdded, this.#onProjectAdded, this);
    this.#workspace.removeEventListener(Workspace.Workspace.Events.ProjectRemoved, this.#onProjectRemoved, this);
  }

  #onListItemKeyDown(event: KeyboardEvent): void {
    switch (event.key) {
      case 'ArrowDown': {
        event.preventDefault();
        this.#selectedIndex = Math.min(this.#selectedIndex + 1, this.#folders.length - 1);
        const targetItem = this.contentElement.querySelectorAll('li')[this.#selectedIndex];
        targetItem?.scrollIntoView({block: 'nearest', inline: 'nearest'});
        targetItem?.focus({preventScroll: true});
        this.requestUpdate();
        break;
      }
      case 'ArrowUp': {
        event.preventDefault();
        this.#selectedIndex = Math.max(this.#selectedIndex - 1, 0);
        const targetItem = this.contentElement.querySelectorAll('li')[this.#selectedIndex];
        targetItem?.scrollIntoView({block: 'nearest', inline: 'nearest'});
        targetItem?.focus({preventScroll: true});
        this.requestUpdate();
        break;
      }
      case 'Enter':
        event.preventDefault();
        this.#onSelectButtonClick();
        break;
    }
  }

  #onSelectButtonClick(): void {
    const selectedFolder = this.#folders[this.#selectedIndex];
    if (selectedFolder.project) {
      this.#dialog.hide();
      this.#onProjectSelected(selectedFolder.project);
    } else {
      void this.#connectToAutomaticFilesystem();
    }
  }

  override performUpdate(): void {
    const loggingEnabled = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue !==
        Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING;

    const viewInput = {
      folders: this.#folders,
      selectedIndex: this.#selectedIndex,
      selectProjectRootText: loggingEnabled ? lockedString(UIStringsNotTranslate.selectProjectRoot) :
                                              lockedString(UIStringsNotTranslate.selectProjectRootNoLogging),
      showAutomaticWorkspaceNudge: this.#automaticFileSystemManager.automaticFileSystem === null &&
          this.#automaticFileSystemManager.availability === 'available',
      onProjectSelected: (index: number) => {
        this.#selectedIndex = index;
        this.requestUpdate();
      },
      onSelectButtonClick: this.#onSelectButtonClick.bind(this),
      onCancelButtonClick: () => {
        this.#dialog.hide();
      },
      onAddFolderButtonClick: () => {
        void this.#addFileSystem();
      },
      onListItemKeyDown: this.#onListItemKeyDown.bind(this),
    };

    this.#view(viewInput, undefined, this.contentElement);
  }

  async #addFileSystem(): Promise<void> {
    await Persistence.IsolatedFileSystemManager.IsolatedFileSystemManager.instance().addFileSystem();
    this.contentElement?.querySelector('[aria-label="Select"]')?.shadowRoot?.querySelector('button')?.focus();
  }

  async #connectToAutomaticFilesystem(): Promise<void> {
    const success = await this.#automaticFileSystemManager.connectAutomaticFileSystem(/* addIfMissing= */ true);
    // In the success-case, we will receive a 'ProjectAdded' event and handle it in '#onProjectAdded'.
    // Only the failure-case is handled here.
    if (!success) {
      this.#dialog.hide();
    }
  }

  #updateProjectsAndFolders(): void {
    this.#folders = [];
    const automaticFileSystem = this.#automaticFileSystemManager.automaticFileSystem;

    // The automatic workspace folder is always added in first position.
    if (automaticFileSystem) {
      this.#folders.push({
        name: Common.ParsedURL.ParsedURL.extractName(automaticFileSystem.root),
        path: automaticFileSystem.root,
        automaticFileSystem,
      });
    }

    const projects = this.#workspace.projectsForType(Workspace.Workspace.projectTypes.FileSystem)
                         .filter(
                             project => project instanceof Persistence.FileSystemWorkspaceBinding.FileSystem &&
                                 project.fileSystem().type() ===
                                     Persistence.PlatformFileSystem.PlatformFileSystemType.WORKSPACE_PROJECT);
    for (const project of projects) {
      // Deduplication prevents a connected automatic workspace folder from being listed twice.
      if (automaticFileSystem && project === this.#workspace.projectForFileSystemRoot(automaticFileSystem.root)) {
        this.#folders[0].project = project;
        continue;
      }

      this.#folders.push({
        name: Common.ParsedURL.ParsedURL.encodedPathToRawPathString(
            project.displayName() as Platform.DevToolsPath.EncodedPathString),
        path: Common.ParsedURL.ParsedURL.urlToRawPathString(
            project.id() as Platform.DevToolsPath.UrlString, Host.Platform.isWin()),
        project,
      });
    }
  }

  #onProjectAdded(event: Common.EventTarget.EventTargetEvent<Workspace.Workspace.Project>): void {
    const addedProject = event.data;

    // After connecting to an automatic workspace folder, wait for the 'projectAdded' event,
    // then close the dialog and continue with the selected project.
    const automaticFileSystem = this.#automaticFileSystemManager.automaticFileSystem;
    if (automaticFileSystem && addedProject === this.#workspace.projectForFileSystemRoot(automaticFileSystem.root)) {
      this.#dialog.hide();
      this.#onProjectSelected(addedProject);
      return;
    }

    this.#updateProjectsAndFolders();
    const projectIndex = this.#folders.findIndex(folder => folder.project === addedProject);
    if (projectIndex !== -1) {
      this.#selectedIndex = projectIndex;
    }
    this.requestUpdate();
    void this.updateComplete.then(() => {
      this.contentElement?.querySelector('.selected')?.scrollIntoView();
    });
  }

  #onProjectRemoved(): void {
    const selectedProject = (this.#selectedIndex >= 0 && this.#selectedIndex < this.#folders.length) ?
        this.#folders[this.#selectedIndex].project :
        null;
    this.#updateProjectsAndFolders();

    if (selectedProject) {
      const projectIndex = this.#folders.findIndex(folder => folder.project === selectedProject);

      // If the previously selected project still exists, select it again.
      // If the previously selected project has been removed, select the project which is now in its
      // position. If the previously selected and now removed project was in last position, select
      // the project which is now in last position.
      this.#selectedIndex =
          projectIndex === -1 ? Math.min(this.#folders.length - 1, this.#selectedIndex) : projectIndex;
    } else {
      this.#selectedIndex = 0;
    }
    this.requestUpdate();
  }

  static show(
      onProjectSelected: (project: Workspace.Workspace.Project) => void,
      currentProject?: Workspace.Workspace.Project): void {
    const dialog = new UI.Dialog.Dialog('select-workspace');
    dialog.setAriaLabel(UIStringsNotTranslate.selectFolderAccessibleLabel);
    dialog.setMaxContentSize(new Geometry.Size(384, 340));
    dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SET_EXACT_WIDTH_MAX_HEIGHT);
    dialog.setDimmed(true);

    new SelectWorkspaceDialog({dialog, onProjectSelected, currentProject}).show(dialog.contentElement);
    dialog.show();
  }
}
