// 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 Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as AiCodeCompletion from '../../models/ai_code_completion/ai_code_completion.js';
import * as AiCodeGeneration from '../../models/ai_code_generation/ai_code_generation.js';
import type * as Workspace from '../../models/workspace/workspace.js';
import type * as CodeMirror from '../../third_party/codemirror.next/codemirror.next.js';
import * as TextEditor from '../../ui/components/text_editor/text_editor.js';
import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as PanelCommon from '../common/common.js';

import {Plugin} from './Plugin.js';

const DISCLAIMER_TOOLTIP_ID = 'sources-ai-code-completion-disclaimer-tooltip';
const SPINNER_TOOLTIP_ID = 'sources-ai-code-completion-spinner-tooltip';
const CITATIONS_TOOLTIP_ID = 'sources-ai-code-completion-citations-tooltip';

export class AiCodeCompletionPlugin extends Plugin {
  #editor?: TextEditor.TextEditor.TextEditor;
  #aiCodeCompletionDisclaimer?: PanelCommon.AiCodeCompletionDisclaimer;
  #aiCodeCompletionDisclaimerContainer = document.createElement('div');
  #aiCodeCompletionDisclaimerToolbarItem = new UI.Toolbar.ToolbarItem(this.#aiCodeCompletionDisclaimerContainer);
  #aiCodeCompletionCitationsToolbar?: PanelCommon.AiCodeCompletionSummaryToolbar;
  #aiCodeCompletionCitationsToolbarContainer = document.createElement('div');
  #aiCodeCompletionCitationsToolbarAttached = false;
  aiCodeCompletionConfig: TextEditor.AiCodeCompletionProvider.AiCodeCompletionConfig;
  #aiCodeCompletionProvider: TextEditor.AiCodeCompletionProvider.AiCodeCompletionProvider;

  constructor(uiSourceCode: Workspace.UISourceCode.UISourceCode) {
    super(uiSourceCode);
    const devtoolsLocale = i18n.DevToolsLocale.DevToolsLocale.instance();
    if (!AiCodeCompletion.AiCodeCompletion.AiCodeCompletion.isAiCodeCompletionEnabled(devtoolsLocale.locale)) {
      throw new Error('AI code completion feature is not enabled.');
    }

    this.aiCodeCompletionConfig = {
      completionContext: {
        additionalFiles: this.uiSourceCode.url().startsWith('snippet://') ? [{
          path: 'devtools-console-context.js',
          content: AiCodeCompletion.AiCodeCompletion.consoleAdditionalContextFileContent,
          included_reason: Host.AidaClient.Reason.RELATED_FILE,
        }] :
                                                                            undefined,
        inferenceLanguage: this.#getInferenceLanguage()
      },
      generationContext: {
        additionalPreambleContext: this.uiSourceCode.url().startsWith('snippet://') ?
            AiCodeGeneration.AiCodeGeneration.additionalContextForConsole :
            undefined,
      },
      onFeatureEnabled: () => {
        this.#setupAiCodeCompletion();
      },
      onFeatureDisabled: () => {
        this.#cleanupAiCodeCompletion();
      },
      onSuggestionAccepted: this.#onAiCodeCompletionSuggestionAccepted.bind(this),
      onRequestTriggered: this.#onAiRequestTriggered.bind(this),
      onResponseReceived: this.#onAiResponseReceived.bind(this),
      panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor.SOURCES,
    };
    this.#aiCodeCompletionProvider =
        TextEditor.AiCodeCompletionProvider.AiCodeCompletionProvider.createInstance(this.aiCodeCompletionConfig);
    this.#aiCodeCompletionDisclaimerContainer.classList.add('ai-code-completion-disclaimer-container');
    this.#aiCodeCompletionDisclaimerContainer.style.paddingInline = 'var(--sys-size-3)';
  }

  static override accepts(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean {
    return uiSourceCode.contentType().hasScripts() || uiSourceCode.contentType().hasStyleSheets();
  }

  override dispose(): void {
    this.#aiCodeCompletionProvider.dispose();
    super.dispose();
  }

  override editorInitialized(editor: TextEditor.TextEditor.TextEditor): void {
    this.#editor = editor;
    this.#aiCodeCompletionProvider.editorInitialized(editor);
    this.#editor.editor.dispatch({
      effects: TextEditor.AiCodeCompletionProvider.setAiCodeCompletionTeaserMode.of(
          TextEditor.AiCodeCompletionProvider.AiCodeCompletionTeaserMode.ON)
    });
  }

  override editorExtension(): CodeMirror.Extension {
    return this.#aiCodeCompletionProvider.extension();
  }

  override rightToolbarItems(): UI.Toolbar.ToolbarItem[] {
    return [this.#aiCodeCompletionDisclaimerToolbarItem];
  }

  #setupAiCodeCompletion(): void {
    this.#createAiCodeCompletionDisclaimer();
    this.#createAiCodeCompletionCitationsToolbar();
  }

  #createAiCodeCompletionDisclaimer(): void {
    if (this.#aiCodeCompletionDisclaimer) {
      return;
    }
    this.#aiCodeCompletionDisclaimer = new PanelCommon.AiCodeCompletionDisclaimer();
    this.#aiCodeCompletionDisclaimer.disclaimerTooltipId = DISCLAIMER_TOOLTIP_ID;
    this.#aiCodeCompletionDisclaimer.spinnerTooltipId = SPINNER_TOOLTIP_ID;
    this.#aiCodeCompletionDisclaimer.panel = AiCodeCompletion.AiCodeCompletion.ContextFlavor.SOURCES;
    this.#aiCodeCompletionDisclaimer.show(this.#aiCodeCompletionDisclaimerContainer, undefined, true);
  }

  #createAiCodeCompletionCitationsToolbar(): void {
    if (this.#aiCodeCompletionCitationsToolbar) {
      return;
    }
    this.#aiCodeCompletionCitationsToolbar = new PanelCommon.AiCodeCompletionSummaryToolbar({
      citationsTooltipId: CITATIONS_TOOLTIP_ID,
      hasTopBorder: true,
      panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor.SOURCES
    });
    this.#aiCodeCompletionCitationsToolbar.show(this.#aiCodeCompletionCitationsToolbarContainer, undefined, true);
  }

  #attachAiCodeCompletionCitationsToolbar(): void {
    if (this.#editor) {
      this.#editor.dispatch({
        effects: SourceFrame.SourceFrame.addSourceFrameInfobar.of(
            {element: this.#aiCodeCompletionCitationsToolbarContainer, order: 100})
      });
      this.#aiCodeCompletionCitationsToolbarAttached = true;
    }
  }

  #removeAiCodeCompletionCitationsToolbar(): void {
    this.#aiCodeCompletionCitationsToolbar?.detach();
    if (this.#editor) {
      this.#editor.dispatch({
        effects: SourceFrame.SourceFrame.removeSourceFrameInfobar.of(
            {element: this.#aiCodeCompletionCitationsToolbarContainer})
      });
      this.#aiCodeCompletionCitationsToolbarAttached = false;
    }
  }

  #cleanupAiCodeCompletion(): void {
    this.#aiCodeCompletionDisclaimerContainer.removeChildren();
    this.#aiCodeCompletionDisclaimer = undefined;
    this.#removeAiCodeCompletionCitationsToolbar();
  }

  #onAiRequestTriggered = (): void => {
    if (this.#aiCodeCompletionDisclaimer) {
      this.#aiCodeCompletionDisclaimer.loading = true;
    }
  };

  #onAiResponseReceived = (): void => {
    if (this.#aiCodeCompletionDisclaimer) {
      this.#aiCodeCompletionDisclaimer.loading = false;
    }
  };

  #onAiCodeCompletionSuggestionAccepted(citations: Host.AidaClient.Citation[]): void {
    if (!this.#aiCodeCompletionCitationsToolbar || citations.length === 0) {
      return;
    }
    const citationsUri = citations.map(citation => citation.uri).filter((uri): uri is string => Boolean(uri));
    this.#aiCodeCompletionCitationsToolbar.updateCitations(citationsUri);
    if (!this.#aiCodeCompletionCitationsToolbarAttached && citationsUri.length > 0) {
      this.#attachAiCodeCompletionCitationsToolbar();
    }
  }

  #getInferenceLanguage(): Host.AidaClient.AidaInferenceLanguage|undefined {
    const mimeType = this.uiSourceCode.mimeType();
    switch (mimeType) {
      case 'application/javascript':
      case 'application/ecmascript':
      case 'application/x-ecmascript':
      case 'application/x-javascript':
      case 'text/ecmascript':
      case 'text/javascript1.0':
      case 'text/javascript1.1':
      case 'text/javascript1.2':
      case 'text/javascript1.3':
      case 'text/javascript1.4':
      case 'text/javascript1.5':
      case 'text/jscript':
      case 'text/livescript ':
      case 'text/x-ecmascript':
      case 'text/x-javascript':
      case 'text/javascript':
      case 'text/jsx':
        return Host.AidaClient.AidaInferenceLanguage.JAVASCRIPT;
      case 'text/typescript':
      case 'text/typescript-jsx':
      case 'application/typescript':
        return Host.AidaClient.AidaInferenceLanguage.TYPESCRIPT;
      case 'text/css':
        return Host.AidaClient.AidaInferenceLanguage.CSS;
      case 'text/html':
        return Host.AidaClient.AidaInferenceLanguage.HTML;
      case 'text/x-python':
      case 'application/python':
        return Host.AidaClient.AidaInferenceLanguage.PYTHON;
      case 'text/x-java':
      case 'text/x-java-source':
        return Host.AidaClient.AidaInferenceLanguage.JAVA;
      case 'text/x-c++src':
      case 'text/x-csrc':
      case 'text/x-c':
        return Host.AidaClient.AidaInferenceLanguage.CPP;
      case 'application/json':
      case 'application/manifest+json':
        return Host.AidaClient.AidaInferenceLanguage.JSON;
      case 'text/markdown':
        return Host.AidaClient.AidaInferenceLanguage.MARKDOWN;
      case 'application/xml':
      case 'application/xhtml+xml':
      case 'text/xml':
        return Host.AidaClient.AidaInferenceLanguage.XML;
      case 'text/x-go':
        return Host.AidaClient.AidaInferenceLanguage.GO;
      case 'application/x-sh':
      case 'text/x-sh':
        return Host.AidaClient.AidaInferenceLanguage.BASH;
      case 'text/x-kotlin':
        return Host.AidaClient.AidaInferenceLanguage.KOTLIN;
      case 'text/x-vue':
      case 'text/x.vue':
        return Host.AidaClient.AidaInferenceLanguage.VUE;
      case 'application/vnd.dart':
        return Host.AidaClient.AidaInferenceLanguage.DART;
      default:
        return undefined;
    }
  }
}
