// Copyright 2019 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 SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import * as UI from '../../ui/legacy/legacy.js';
import {render} from '../../ui/lit/lit.js';

import {type ContrastIssue, CSSOverviewCompletedView, type OverviewData} from './CSSOverviewCompletedView.js';
import {CSSOverviewModel, type GlobalStyleStats} from './CSSOverviewModel.js';
import {CSSOverviewProcessingView} from './CSSOverviewProcessingView.js';
import {CSSOverviewStartView} from './CSSOverviewStartView.js';
import type {UnusedDeclaration} from './CSSOverviewUnusedDeclarations.js';

const {widget} = UI.Widget;

interface ViewInput {
  state: 'start'|'processing'|'completed';
  onStartCapture: () => void;
  onCancel: () => void;
  onReset: () => void;
  overviewData: OverviewData;
  target?: SDK.Target.Target;
}

type View = (input: ViewInput, output: object, target: HTMLElement) => void;

export const DEFAULT_VIEW: View = (input, _output, target) => {
  render(
      input.state === 'start'          ? widget(CSSOverviewStartView, {onStartCapture: input.onStartCapture}) :
          input.state === 'processing' ? widget(CSSOverviewProcessingView, {onCancel: input.onCancel}) :
                                         widget(CSSOverviewCompletedView, {
                                           onReset: input.onReset,
                                           overviewData: input.overviewData,
                                           target: input.target,
                                         }),
      target);
};

export class CSSOverviewPanel extends UI.Panel.Panel implements SDK.TargetManager.Observer {
  #currentUrl: string;
  #model?: CSSOverviewModel;
  #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[]>;
  #state!: 'start'|'processing'|'completed';
  #view: View;

  constructor(view = DEFAULT_VIEW) {
    super('css-overview');
    this.#currentUrl = SDK.TargetManager.TargetManager.instance().inspectedURL();
    SDK.TargetManager.TargetManager.instance().addEventListener(
        SDK.TargetManager.Events.INSPECTED_URL_CHANGED, this.#checkUrlAndResetIfChanged, this);

    this.#view = view;
    SDK.TargetManager.TargetManager.instance().observeTargets(this);
    this.#reset();
  }

  #onStartCapture(): void {
    Host.userMetrics.actionTaken(Host.UserMetrics.Action.CaptureCssOverviewClicked);
    void this.#startOverview();
  }

  #checkUrlAndResetIfChanged(): void {
    if (this.#currentUrl === SDK.TargetManager.TargetManager.instance().inspectedURL()) {
      return;
    }

    this.#currentUrl = SDK.TargetManager.TargetManager.instance().inspectedURL();
    this.#reset();
  }

  targetAdded(target: SDK.Target.Target): void {
    if (target !== SDK.TargetManager.TargetManager.instance().primaryPageTarget()) {
      return;
    }
    this.#model = target.model(CSSOverviewModel) ?? undefined;
  }

  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();
  }

  #renderInitialView(): void {
    this.#state = 'start';
    this.performUpdate();
  }

  #renderOverviewStartedView(): void {
    this.#state = 'processing';
    this.performUpdate();
  }

  #renderOverviewCompletedView(): void {
    this.#state = 'completed';
    this.performUpdate();
  }

  override performUpdate(): void {
    const viewInput = {
      state: this.#state,
      onStartCapture: this.#onStartCapture.bind(this),
      onCancel: this.#reset.bind(this),
      onReset: this.#reset.bind(this),
      target: this.#model?.target(),
      overviewData: {
        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,
      },
    };
    this.#view(viewInput, {}, this.contentElement);
  }

  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.#renderOverviewCompletedView();
  }
}
