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

import type * 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 SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import * as LegacyWrapper from '../../ui/components/legacy_wrapper/legacy_wrapper.js';
import {createIcon} from '../../ui/kit/kit.js';
import * as UI from '../../ui/legacy/legacy.js';

import {IndexedDBTreeElement} from './ApplicationPanelSidebar.js';
import {ExpandableApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js';
import {StorageMetadataView} from './components/components.js';
import type {ResourcesPanel} from './ResourcesPanel.js';
import {ServiceWorkerCacheTreeElement} from './ServiceWorkerCacheTreeElement.js';

const UIStrings = {
  /**
   * @description Label for an item in the Application Panel Sidebar of the Application panel
   * Storage Buckets allow developers to separate site data into buckets so that they can be
   * deleted independently.
   */
  storageBuckets: 'Storage buckets',
  /**
   * @description Text for an item in the Application Panel
   * if no storage buckets are available to show. Storage Buckets allow developers to separate
   * site data into buckets so that they can be
   * deleted independently. https://developer.chrome.com/docs/web-platform/storage-buckets.
   */
  noStorageBuckets: 'No storage buckets detected',
  /**
   * @description Description text in the Application Panel describing the storage buckets tab.
   * Storage Buckets allow developers to separate site data into buckets so that they can be
   * deleted independently. https://developer.chrome.com/docs/web-platform/storage-buckets.
   */
  storageBucketsDescription:
      'On this page you can view and delete storage buckets, and their associated `Storage APIs`.'
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/application/StorageBucketsTreeElement.ts', UIStrings);
export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

export class StorageBucketsTreeParentElement extends ExpandableApplicationPanelTreeElement {
  private bucketTreeElements = new Set<StorageBucketsTreeElement>();

  constructor(storagePanel: ResourcesPanel) {
    super(
        storagePanel, i18nString(UIStrings.storageBuckets), i18nString(UIStrings.noStorageBuckets),
        i18nString(UIStrings.storageBucketsDescription), 'storage-buckets');
    const icon = createIcon('bucket');
    this.setLeadingIcons([icon]);
    this.setLink(
        'https://github.com/WICG/storage-buckets/blob/gh-pages/explainer.md' as Platform.DevToolsPath.UrlString);
  }

  initialize(): void {
    SDK.TargetManager.TargetManager.instance().addModelListener(
        SDK.StorageBucketsModel.StorageBucketsModel, SDK.StorageBucketsModel.Events.BUCKET_ADDED, this.bucketAdded,
        this);
    SDK.TargetManager.TargetManager.instance().addModelListener(
        SDK.StorageBucketsModel.StorageBucketsModel, SDK.StorageBucketsModel.Events.BUCKET_REMOVED, this.bucketRemoved,
        this);
    SDK.TargetManager.TargetManager.instance().addModelListener(
        SDK.StorageBucketsModel.StorageBucketsModel, SDK.StorageBucketsModel.Events.BUCKET_CHANGED, this.bucketChanged,
        this);

    for (const bucketsModel of SDK.TargetManager.TargetManager.instance().models(
             SDK.StorageBucketsModel.StorageBucketsModel)) {
      const buckets = bucketsModel.getBuckets();
      for (const bucket of buckets) {
        this.addBucketTreeElement(bucketsModel, bucket);
      }
    }
  }

  removeBucketsForModel(model: SDK.StorageBucketsModel.StorageBucketsModel): void {
    for (const bucketTreeElement of this.bucketTreeElements) {
      if (bucketTreeElement.model === model) {
        this.removeBucketTreeElement(bucketTreeElement);
      }
    }
  }

  private bucketAdded({data: {model, bucketInfo}}:
                          Common.EventTarget.EventTargetEvent<SDK.StorageBucketsModel.BucketEvent>): void {
    this.addBucketTreeElement(model, bucketInfo);
  }

  private bucketRemoved({data: {model, bucketInfo}}:
                            Common.EventTarget.EventTargetEvent<SDK.StorageBucketsModel.BucketEvent>): void {
    const idbDatabaseTreeElement = this.getBucketTreeElement(model, bucketInfo);
    if (!idbDatabaseTreeElement) {
      return;
    }
    this.removeBucketTreeElement(idbDatabaseTreeElement);
  }

  private bucketChanged({data: {model, bucketInfo}}:
                            Common.EventTarget.EventTargetEvent<SDK.StorageBucketsModel.BucketEvent>): void {
    const idbDatabaseTreeElement = this.getBucketTreeElement(model, bucketInfo);
    if (!idbDatabaseTreeElement) {
      return;
    }
    idbDatabaseTreeElement.bucketInfo = bucketInfo;
  }

  private addBucketTreeElement(
      model: SDK.StorageBucketsModel.StorageBucketsModel, bucketInfo: Protocol.Storage.StorageBucketInfo): void {
    if (bucketInfo.bucket.name === undefined) {
      return;
    }
    const singleBucketTreeElement = new StorageBucketsTreeElement(this.resourcesPanel, model, bucketInfo);
    this.bucketTreeElements.add(singleBucketTreeElement);
    this.appendChild(singleBucketTreeElement);
    singleBucketTreeElement.initialize();
  }

  private removeBucketTreeElement(bucketTreeElement: StorageBucketsTreeElement): void {
    this.removeChild(bucketTreeElement);
    this.bucketTreeElements.delete(bucketTreeElement);
    this.setExpandable(this.bucketTreeElements.size > 0);
  }

  override get itemURL(): Platform.DevToolsPath.UrlString {
    return 'storage-buckets-group://' as Platform.DevToolsPath.UrlString;
  }

  getBucketTreeElement(model: SDK.StorageBucketsModel.StorageBucketsModel, {
    bucket: {storageKey, name},
  }: Protocol.Storage.StorageBucketInfo): StorageBucketsTreeElement|null {
    for (const bucketTreeElement of this.bucketTreeElements) {
      if (bucketTreeElement.model === model && bucketTreeElement.bucketInfo.bucket.storageKey === storageKey &&
          bucketTreeElement.bucketInfo.bucket.name === name) {
        return bucketTreeElement;
      }
    }
    return null;
  }
}

export class StorageBucketsTreeElement extends ExpandableApplicationPanelTreeElement {
  private storageBucketInfo: Protocol.Storage.StorageBucketInfo;
  private bucketModel: SDK.StorageBucketsModel.StorageBucketsModel;
  private view?: LegacyWrapper.LegacyWrapper.LegacyWrapper<UI.Widget.Widget, StorageMetadataView.StorageMetadataView>;

  constructor(
      resourcesPanel: ResourcesPanel, model: SDK.StorageBucketsModel.StorageBucketsModel,
      bucketInfo: Protocol.Storage.StorageBucketInfo) {
    const {bucket} = bucketInfo;
    const {origin} = SDK.StorageKeyManager.parseStorageKey(bucketInfo.bucket.storageKey);
    super(resourcesPanel, `${bucket.name} - ${origin}`, '', '', 'storage-bucket');
    this.bucketModel = model;
    this.storageBucketInfo = bucketInfo;
    const icon = createIcon('database');
    this.setLeadingIcons([icon]);
  }

  initialize(): void {
    const {bucket} = this.bucketInfo;
    const indexedDBTreeElement = new IndexedDBTreeElement(this.resourcesPanel, bucket);
    this.appendChild(indexedDBTreeElement);
    const serviceWorkerCacheTreeElement = new ServiceWorkerCacheTreeElement(this.resourcesPanel, bucket);
    this.appendChild(serviceWorkerCacheTreeElement);
    serviceWorkerCacheTreeElement.initialize();
  }

  override get itemURL(): Platform.DevToolsPath.UrlString {
    const {bucket} = this.bucketInfo;
    return `storage-buckets-group://${bucket.name}/${bucket.storageKey}` as Platform.DevToolsPath.UrlString;
  }

  get model(): SDK.StorageBucketsModel.StorageBucketsModel {
    return this.bucketModel;
  }

  get bucketInfo(): Protocol.Storage.StorageBucketInfo {
    return this.storageBucketInfo;
  }

  set bucketInfo(bucketInfo: Protocol.Storage.StorageBucketInfo) {
    this.storageBucketInfo = bucketInfo;
    if (this.view) {
      this.view.getComponent().setStorageBucket(this.storageBucketInfo);
    }
  }

  override onselect(selectedByUser?: boolean): boolean {
    super.onselect(selectedByUser);
    if (!this.view) {
      this.view =
          LegacyWrapper.LegacyWrapper.legacyWrapper(UI.Widget.Widget, new StorageMetadataView.StorageMetadataView());
      this.view.getComponent().enableStorageBucketControls(this.model);
      this.view.getComponent().setStorageBucket(this.storageBucketInfo);
    }
    this.showView(this.view);
    return false;
  }
}
