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

import * as CSSOverviewComponents from './components/components.js';
import cssOverviewStyles from './cssOverview.css.js';
import type * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as UI from '../../ui/legacy/legacy.js';
import type * as Protocol from '../../generated/protocol.js';

import type {ContrastIssue} from './CSSOverviewCompletedView.js';
import {CSSOverviewCompletedView} from './CSSOverviewCompletedView.js';
import {Events, OverviewController} from './CSSOverviewController.js';
import type {GlobalStyleStats} from './CSSOverviewModel.js';
import {CSSOverviewModel} from './CSSOverviewModel.js';
import {CSSOverviewProcessingView} from './CSSOverviewProcessingView.js';
import type {UnusedDeclaration} from './CSSOverviewUnusedDeclarations.js';

// eslint-disable-next-line @typescript-eslint/naming-convention
let CSSOverviewPanelInstance: CSSOverviewPanel;

export class CSSOverviewPanel extends UI.Panel.Panel implements SDK.TargetManager.Observer {
  readonly #controller: OverviewController;
  readonly #startView: CSSOverviewComponents.CSSOverviewStartView.CSSOverviewStartView;
  readonly #processingView: CSSOverviewProcessingView;
  readonly #completedView: CSSOverviewCompletedView;
  #model?: CSSOverviewModel;
  #target?: SDK.Target.Target;
  #backgroundColors!: Map<string, Set<Protocol.DOM.BackendNodeId>>;
  #textColors!: Map<string, Set<Protocol.DOM.BackendNodeId>>;
  #fillColors!: Map<string, Set<Protocol.DOM.BackendNodeId>>;
  #borderColors!: Map<string, Set<Protocol.DOM.BackendNodeId>>;
  #fontInfo!: Map<string, Map<string, Map<string, Protocol.DOM.BackendNodeId[]>>>;
  #mediaQueries!: Map<string, Protocol.CSS.CSSMedia[]>;
  #unusedDeclarations!: Map<string, UnusedDeclaration[]>;
  #elementCount!: number;
  #globalStyleStats!: GlobalStyleStats;
  #textColorContrastIssues!: Map<string, ContrastIssue[]>;

  private constructor() {
    super('css_overview');

    this.element.classList.add('css-overview-panel');

    this.#controller = new OverviewController();
    this.#startView = new CSSOverviewComponents.CSSOverviewStartView.CSSOverviewStartView();
    this.#startView.addEventListener(
        'overviewstartrequested', () => this.#controller.dispatchEventToListeners(Events.RequestOverviewStart));
    this.#processingView = new CSSOverviewProcessingView(this.#controller);
    this.#completedView = new CSSOverviewCompletedView(this.#controller);

    SDK.TargetManager.TargetManager.instance().observeTargets(this);

    this.#controller.addEventListener(Events.RequestOverviewStart, _event => {
      Host.userMetrics.actionTaken(Host.UserMetrics.Action.CaptureCssOverviewClicked);
      void this.#startOverview();
    }, this);
    this.#controller.addEventListener(Events.OverviewCompleted, this.#overviewCompleted, this);
    this.#controller.addEventListener(Events.Reset, this.#reset, this);
    this.#controller.addEventListener(Events.RequestNodeHighlight, this.#requestNodeHighlight, this);

    this.#reset();
  }

  static instance(): CSSOverviewPanel {
    if (!CSSOverviewPanelInstance) {
      CSSOverviewPanelInstance = new CSSOverviewPanel();
    }
    return CSSOverviewPanelInstance;
  }

  targetAdded(target: SDK.Target.Target): void {
    if (this.#target) {
      return;
    }
    this.#target = target;
    this.#completedView.initializeModels(target);
    const [model] = SDK.TargetManager.TargetManager.instance().models(CSSOverviewModel);
    this.#model = (model as CSSOverviewModel);
  }

  targetRemoved(): void {
  }

  #getModel(): CSSOverviewModel {
    if (!this.#model) {
      throw new Error('Did not retrieve model information yet.');
    }
    return this.#model;
  }

  #reset(): void {
    this.#backgroundColors = new Map();
    this.#textColors = new Map();
    this.#fillColors = new Map();
    this.#borderColors = new Map();
    this.#fontInfo = new Map();
    this.#mediaQueries = new Map();
    this.#unusedDeclarations = new Map();
    this.#elementCount = 0;
    this.#globalStyleStats = {
      styleRules: 0,
      inlineStyles: 0,
      externalSheets: 0,
      stats: {
        // Simple.
        type: 0,
        class: 0,
        id: 0,
        universal: 0,
        attribute: 0,

        // Non-simple.
        nonSimple: 0,
      },
    };
    this.#textColorContrastIssues = new Map();
    this.#renderInitialView();
  }

  #requestNodeHighlight(evt: Common.EventTarget.EventTargetEvent<number>): void {
    this.#getModel().highlightNode((evt.data as Protocol.DOM.BackendNodeId));
  }

  #renderInitialView(): void {
    this.#processingView.hideWidget();
    this.#completedView.hideWidget();

    this.contentElement.append(this.#startView);
    this.#startView.show();
  }

  #renderOverviewStartedView(): void {
    this.#startView.hide();
    this.#completedView.hideWidget();

    this.#processingView.show(this.contentElement);
  }

  #renderOverviewCompletedView(): void {
    this.#startView.hide();
    this.#processingView.hideWidget();

    this.#completedView.show(this.contentElement);
    this.#completedView.setOverviewData({
      backgroundColors: this.#backgroundColors,
      textColors: this.#textColors,
      textColorContrastIssues: this.#textColorContrastIssues,
      fillColors: this.#fillColors,
      borderColors: this.#borderColors,
      globalStyleStats: this.#globalStyleStats,
      fontInfo: this.#fontInfo,
      elementCount: this.#elementCount,
      mediaQueries: this.#mediaQueries,
      unusedDeclarations: this.#unusedDeclarations,
    });
  }

  async #startOverview(): Promise<void> {
    this.#renderOverviewStartedView();

    const model = this.#getModel();
    const [globalStyleStats, { elementCount, backgroundColors, textColors, textColorContrastIssues, fillColors, borderColors, fontInfo, unusedDeclarations }, mediaQueries] = await Promise.all([
      model.getGlobalStylesheetStats(),
      model.getNodeStyleStats(),
      model.getMediaQueries(),
    ]);

    if (elementCount) {
      this.#elementCount = elementCount;
    }

    if (globalStyleStats) {
      this.#globalStyleStats = globalStyleStats;
    }

    if (mediaQueries) {
      this.#mediaQueries = mediaQueries;
    }

    if (backgroundColors) {
      this.#backgroundColors = backgroundColors;
    }

    if (textColors) {
      this.#textColors = textColors;
    }

    if (textColorContrastIssues) {
      this.#textColorContrastIssues = textColorContrastIssues;
    }

    if (fillColors) {
      this.#fillColors = fillColors;
    }

    if (borderColors) {
      this.#borderColors = borderColors;
    }

    if (fontInfo) {
      this.#fontInfo = fontInfo;
    }

    if (unusedDeclarations) {
      this.#unusedDeclarations = unusedDeclarations;
    }

    this.#controller.dispatchEventToListeners(Events.OverviewCompleted);
  }

  #overviewCompleted(): void {
    this.#renderOverviewCompletedView();
  }
  wasShown(): void {
    super.wasShown();
    this.registerCSSFiles([cssOverviewStyles]);
  }
}
