// 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) 2008 Nokia Inc.  All rights reserved.
 * Copyright (C) 2013 Samsung Electronics. 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.
 *
 * THIS SOFTWARE IS PROVIDED ``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 INC. OR
 * 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 * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import type * as Platform from '../../core/platform/platform.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';

import {DOMStorage} from './DOMStorageModel.js';
import {KeyValueStorageItemsView} from './KeyValueStorageItemsView.js';

const UIStrings = {
  /**
   * @description Name for the "DOM Storage Items" table that shows the content of the DOM Storage.
   */
  domStorageItems: 'DOM Storage Items',
  /**
   * @description Text for announcing that the "DOM Storage Items" table was cleared, that is, all
   * entries were deleted.
   */
  domStorageItemsCleared: 'DOM Storage Items cleared',
  /**
   * @description Text for announcing a DOM Storage key/value item has been deleted
   */
  domStorageItemDeleted: 'The storage item was deleted.',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/application/DOMStorageItemsView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class DOMStorageItemsView extends KeyValueStorageItemsView {
  private domStorage: DOMStorage;
  private eventListeners: Common.EventTarget.EventDescriptor[];

  constructor(domStorage: DOMStorage) {
    super(i18nString(UIStrings.domStorageItems), 'dom-storage', true);

    this.domStorage = domStorage;
    if (domStorage.storageKey) {
      this.toolbar?.setStorageKey(domStorage.storageKey);
    }

    this.element.classList.add('storage-view', 'table');

    this.showPreview(null, null);

    this.eventListeners = [];
    this.setStorage(domStorage);
  }

  protected createPreview(key: string, value: string): Promise<UI.Widget.Widget|null> {
    const protocol = this.domStorage.isLocalStorage ? 'localstorage' : 'sessionstorage';
    const url = `${protocol}://${key}` as Platform.DevToolsPath.UrlString;
    const provider = TextUtils.StaticContentProvider.StaticContentProvider.fromString(
        url,
        Common.ResourceType.resourceTypes.XHR,
        value,
    );
    return SourceFrame.PreviewFactory.PreviewFactory.createPreview(
        provider,
        'text/plain',
    );
  }

  setStorage(domStorage: DOMStorage): void {
    Common.EventTarget.removeEventListeners(this.eventListeners);
    this.domStorage = domStorage;
    const storageKind = domStorage.isLocalStorage ? 'local-storage-data' : 'session-storage-data';
    this.element.setAttribute('jslog', `${VisualLogging.pane().context(storageKind)}`);
    if (domStorage.storageKey) {
      this.toolbar?.setStorageKey(domStorage.storageKey);
    }
    this.eventListeners = [
      this.domStorage.addEventListener(DOMStorage.Events.DOM_STORAGE_ITEMS_CLEARED, this.domStorageItemsCleared, this),
      this.domStorage.addEventListener(DOMStorage.Events.DOM_STORAGE_ITEM_REMOVED, this.domStorageItemRemoved, this),
      this.domStorage.addEventListener(DOMStorage.Events.DOM_STORAGE_ITEM_ADDED, this.domStorageItemAdded, this),
      this.domStorage.addEventListener(DOMStorage.Events.DOM_STORAGE_ITEM_UPDATED, this.domStorageItemUpdated, this),
    ];
    this.refreshItems();
  }

  private domStorageItemsCleared(): void {
    if (!this.isShowing()) {
      return;
    }

    this.itemsCleared();
  }

  override itemsCleared(): void {
    super.itemsCleared();
    UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.domStorageItemsCleared));
  }

  private domStorageItemRemoved(event: Common.EventTarget.EventTargetEvent<DOMStorage.DOMStorageItemRemovedEvent>):
      void {
    if (!this.isShowing()) {
      return;
    }

    this.itemRemoved(event.data.key);
  }

  override itemRemoved(key: string): void {
    super.itemRemoved(key);
    UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.domStorageItemDeleted));
  }

  private domStorageItemAdded(event: Common.EventTarget.EventTargetEvent<DOMStorage.DOMStorageItemAddedEvent>): void {
    if (!this.isShowing()) {
      return;
    }

    this.itemAdded(event.data.key, event.data.value);
  }

  private domStorageItemUpdated(event: Common.EventTarget.EventTargetEvent<DOMStorage.DOMStorageItemUpdatedEvent>):
      void {
    if (!this.isShowing()) {
      return;
    }

    this.itemUpdated(event.data.key, event.data.value);
  }

  override refreshItems(): void {
    void this.#refreshItems();
  }

  async #refreshItems(): Promise<void> {
    const items = await this.domStorage.getItems();
    if (!items || !this.toolbar) {
      return;
    }
    const {filterRegex} = this.toolbar;
    const filteredItems = items.map(item => ({key: item[0], value: item[1]}))
                              .filter(item => filterRegex?.test(`${item.key} ${item.value}`) ?? true);
    this.showItems(filteredItems);
  }

  override deleteAllItems(): void {
    this.domStorage.clear();
    // explicitly clear the view because the event won't be fired when it has no items
    this.domStorageItemsCleared();
  }

  protected removeItem(key: string): void {
    this.domStorage?.removeItem(key);
  }

  protected setItem(key: string, value: string): void {
    this.domStorage?.setItem(key, value);
  }
}
