// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable @devtools/no-imperative-dom-api */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) IBM Corp. 2009  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import '../../legacy.js';

import * as Common from '../../../../core/common/common.js';
import * as i18n from '../../../../core/i18n/i18n.js';
import * as FormatterActions from '../../../../entrypoints/formatter_worker/FormatterActions.js';  // eslint-disable-line @devtools/es-modules-import
import * as TextUtils from '../../../../models/text_utils/text_utils.js';
import * as UI from '../../legacy.js';

import resourceSourceFrameStyles from './resourceSourceFrame.css.js';
import {type RevealPosition, SourceFrameImpl, type SourceFrameOptions} from './SourceFrame.js';

const UIStrings = {
  /**
   * @description Text to find an item
   */
  find: 'Find',
} as const;
const str_ = i18n.i18n.registerUIStrings('ui/legacy/components/source_frame/ResourceSourceFrame.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

export class ResourceSourceFrame extends SourceFrameImpl {
  readonly #resource: TextUtils.ContentProvider.ContentProvider;
  readonly #givenContentType: string;

  constructor(
      resource: TextUtils.ContentProvider.ContentProvider, givenContentType: string, options?: SourceFrameOptions) {
    const isStreamingProvider = TextUtils.ContentProvider.isStreamingContentProvider(resource);

    const lazyContent = isStreamingProvider ?
        () => resource.requestStreamingContent().then(TextUtils.StreamingContentData.asContentDataOrError) :
        () => resource.requestContentData();
    super(lazyContent, options);

    this.#givenContentType = givenContentType;
    this.#resource = resource;
    if (isStreamingProvider) {
      void resource.requestStreamingContent().then(streamingContent => {
        if (!TextUtils.StreamingContentData.isError(streamingContent)) {
          streamingContent.addEventListener(TextUtils.StreamingContentData.Events.CHUNK_ADDED, () => {
            void this.setContentDataOrError(Promise.resolve(streamingContent.content()));
          });
        }
      });
    }
  }

  static createSearchableView(resource: TextUtils.ContentProvider.ContentProvider, contentType: string):
      UI.Widget.Widget {
    return new SearchableContainer(resource, contentType);
  }

  protected override getContentType(): string {
    return this.#givenContentType;
  }

  get resource(): TextUtils.ContentProvider.ContentProvider {
    return this.#resource;
  }

  protected override populateTextAreaContextMenu(
      contextMenu: UI.ContextMenu.ContextMenu, lineNumber: number, columnNumber: number): void {
    super.populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber);
    contextMenu.appendApplicableItems(this.#resource);
  }
}

export class SearchableContainer extends UI.Widget.VBox {
  private readonly sourceFrame: ResourceSourceFrame;

  constructor(resource: TextUtils.ContentProvider.ContentProvider, contentType: string, element?: HTMLElement) {
    super(element, {useShadowDom: true});
    this.registerRequiredCSS(resourceSourceFrameStyles);
    const simpleContentType = Common.ResourceType.ResourceType.simplifyContentType(contentType);
    const sourceFrame = new ResourceSourceFrame(resource, simpleContentType);
    this.sourceFrame = sourceFrame;
    const canPrettyPrint = FormatterActions.FORMATTABLE_MEDIA_TYPES.includes(simpleContentType);
    sourceFrame.setCanPrettyPrint(canPrettyPrint, true /* autoPrettyPrint */);
    const searchableView = new UI.SearchableView.SearchableView(sourceFrame, sourceFrame);
    searchableView.element.classList.add('searchable-view');
    searchableView.setPlaceholder(i18nString(UIStrings.find));
    sourceFrame.show(searchableView.element);
    sourceFrame.setSearchableView(searchableView);
    searchableView.show(this.contentElement);

    const toolbar = this.contentElement.createChild('devtools-toolbar', 'toolbar');
    void sourceFrame.toolbarItems().then(items => {
      items.map(item => toolbar.appendToolbarItem(item));
    });
  }

  async revealPosition(position: RevealPosition): Promise<void> {
    this.sourceFrame.revealPosition(position, true);
  }
}
