// 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.
/* eslint-disable rulesdir/no-lit-render-outside-of-view */

import '../../../../ui/components/icon_button/icon_button.js';
import '../../../../ui/components/report_view/report_view.js';
import './PreloadingMismatchedHeadersGrid.js';
import './MismatchedPreloadingGrid.js';

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 {assertNotNullOrUndefined} from '../../../../core/platform/platform.js';
import * as SDK from '../../../../core/sdk/sdk.js';
import * as Protocol from '../../../../generated/protocol.js';
import * as LegacyWrapper from '../../../../ui/components/legacy_wrapper/legacy_wrapper.js';
import * as RenderCoordinator from '../../../../ui/components/render_coordinator/render_coordinator.js';
import * as UI from '../../../../ui/legacy/legacy.js';
import * as Lit from '../../../../ui/lit/lit.js';
import * as VisualLogging from '../../../../ui/visual_logging/visual_logging.js';
import * as PreloadingHelper from '../helper/helper.js';

import type * as MismatchedPreloadingGrid from './MismatchedPreloadingGrid.js';
import {prefetchFailureReason, prerenderFailureReason} from './PreloadingString.js';
import usedPreloadingStyles from './usedPreloadingView.css.js';

const {html} = Lit;

const UIStrings = {
  /**
   * @description Header for preloading status.
   */
  speculativeLoadingStatusForThisPage: 'Speculative loading status for this page',
  /**
   * @description Label for failure reason of preloading
   */
  detailsFailureReason: 'Failure reason',
  /**
   * @description Message that tells this page was prerendered.
   */
  downgradedPrefetchUsed:
      'The initiating page attempted to prerender this page\'s URL. The prerender failed, but the resulting response body was still used as a prefetch.',
  /**
   * @description Message that tells this page was prefetched.
   */
  prefetchUsed: 'This page was successfully prefetched.',
  /**
   * @description Message that tells this page was prerendered.
   */
  prerenderUsed: 'This page was successfully prerendered.',
  /**
   * @description Message that tells this page was prefetched.
   */
  prefetchFailed:
      'The initiating page attempted to prefetch this page\'s URL, but the prefetch failed, so a full navigation was performed instead.',
  /**
   * @description Message that tells this page was prerendered.
   */
  prerenderFailed:
      'The initiating page attempted to prerender this page\'s URL, but the prerender failed, so a full navigation was performed instead.',
  /**
   * @description Message that tells this page was not preloaded.
   */
  noPreloads: 'The initiating page did not attempt to speculatively load this page\'s URL.',
  /**
   * @description Header for current URL.
   */
  currentURL: 'Current URL',
  /**
   * @description Header for mismatched preloads.
   */
  preloadedURLs: 'URLs being speculatively loaded by the initiating page',
  /**
   * @description Header for summary.
   */
  speculationsInitiatedByThisPage: 'Speculations initiated by this page',
  /**
   * @description Link text to reveal rules.
   */
  viewAllRules: 'View all speculation rules',
  /**
   * @description Link text to reveal preloads.
   */
  viewAllSpeculations: 'View all speculations',
  /**
   * @description Link to learn more about Preloading
   */
  learnMore: 'Learn more: Speculative loading on developer.chrome.com',
  /**
   * @description Header for the table of mismatched network request header.
   */
  mismatchedHeadersDetail: 'Mismatched HTTP request headers',
  /**
   * @description Label for badge, indicating speculative load successfully used for this page.
   */
  badgeSuccess: 'Success',
  /**
   * @description Label for badge, indicating speculative load failed for this page.
   */
  badgeFailure: 'Failure',
  /**
   * @description Label for badge, indicating no speculative loads used for this page.
   */
  badgeNoSpeculativeLoads: 'No speculative loads',
  /**
   * @description Label for badge, indicating how many not triggered speculations there are.
   */
  badgeNotTriggeredWithCount: '{n, plural, =1 {# not triggered} other {# not triggered}}',
  /**
   * @description Label for badge, indicating how many in progress speculations there are.
   */
  badgeInProgressWithCount: '{n, plural, =1 {# in progress} other {# in progress}}',
  /**
   * @description Label for badge, indicating how many succeeded speculations there are.
   */
  badgeSuccessWithCount: '{n, plural, =1 {# success} other {# success}}',
  /**
   * @description Label for badge, indicating how many failed speculations there are.
   */
  badgeFailureWithCount: '{n, plural, =1 {# failure} other {# failures}}',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/application/preloading/components/UsedPreloadingView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

export interface UsedPreloadingViewData {
  pageURL: Platform.DevToolsPath.UrlString;
  previousAttempts: SDK.PreloadingModel.PreloadingAttempt[];
  currentAttempts: SDK.PreloadingModel.PreloadingAttempt[];
}

export const enum UsedKind {
  DOWNGRADED_PRERENDER_TO_PREFETCH_AND_USED = 'DowngradedPrerenderToPrefetchAndUsed',
  PREFETCH_USED = 'PrefetchUsed',
  PRERENDER_USED = 'PrerenderUsed',
  PREFETCH_FAILED = 'PrefetchFailed',
  PRERENDER_FAILED = 'PrerenderFailed',
  NO_PRELOADS = 'NoPreloads',
}

/**
 * TODO(kenoss): Rename this class and file once https://crrev.com/c/4933567 landed.
 * This also shows summary of speculations initiated by this page.
 **/
export class UsedPreloadingView extends LegacyWrapper.LegacyWrapper.WrappableComponent<UI.Widget.VBox> {
  readonly #shadow = this.attachShadow({mode: 'open'});
  #data: UsedPreloadingViewData = {
    pageURL: '' as Platform.DevToolsPath.UrlString,
    previousAttempts: [],
    currentAttempts: [],
  };

  set data(data: UsedPreloadingViewData) {
    this.#data = data;
    void this.#render();
  }

  async #render(): Promise<void> {
    await RenderCoordinator.write('UsedPreloadingView render', () => {
      Lit.render(this.#renderTemplate(), this.#shadow, {host: this});
    });
  }

  #renderTemplate(): Lit.LitTemplate {
    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    return html`
      <style>${usedPreloadingStyles}</style>
      <devtools-report>
        ${this.#speculativeLoadingStatusForThisPageSections()}

        <devtools-report-divider></devtools-report-divider>

        ${this.#speculationsInitiatedByThisPageSummarySections()}

        <devtools-report-divider></devtools-report-divider>

        <devtools-report-section>
          ${UI.XLink.XLink.create('https://developer.chrome.com/blog/prerender-pages/', i18nString(UIStrings.learnMore), 'link', undefined, 'learn-more')}
        </devtools-report-section>
      </devtools-report>
    `;
    // clang-format on
  }

  #speculativeLoadingStatusForThisPageSections(): Lit.LitTemplate {
    const pageURL = Common.ParsedURL.ParsedURL.urlWithoutHash(this.#data.pageURL);
    const forThisPage = this.#data.previousAttempts.filter(
        attempt => Common.ParsedURL.ParsedURL.urlWithoutHash(attempt.key.url) === pageURL);
    const prefetch =
        forThisPage.filter(attempt => attempt.key.action === Protocol.Preload.SpeculationAction.Prefetch)[0];
    const prerender =
        forThisPage.filter(attempt => attempt.key.action === Protocol.Preload.SpeculationAction.Prerender)[0];

    let kind = UsedKind.NO_PRELOADS;
    // Prerender -> prefetch downgrade case
    //
    // This code does not handle the case SpecRules designate these preloads rather than prerenderer automatically downgrade prerendering.
    // TODO(https://crbug.com/1410709): Improve this logic once automatic downgrade implemented.
    if (prerender?.status === SDK.PreloadingModel.PreloadingStatus.FAILURE &&
        prefetch?.status === SDK.PreloadingModel.PreloadingStatus.SUCCESS) {
      kind = UsedKind.DOWNGRADED_PRERENDER_TO_PREFETCH_AND_USED;
    } else if (prefetch?.status === SDK.PreloadingModel.PreloadingStatus.SUCCESS) {
      kind = UsedKind.PREFETCH_USED;
    } else if (prerender?.status === SDK.PreloadingModel.PreloadingStatus.SUCCESS) {
      kind = UsedKind.PRERENDER_USED;
    } else if (prefetch?.status === SDK.PreloadingModel.PreloadingStatus.FAILURE) {
      kind = UsedKind.PREFETCH_FAILED;
    } else if (prerender?.status === SDK.PreloadingModel.PreloadingStatus.FAILURE) {
      kind = UsedKind.PRERENDER_FAILED;
    } else {
      kind = UsedKind.NO_PRELOADS;
    }

    let badge;
    let basicMessage;
    switch (kind) {
      case UsedKind.DOWNGRADED_PRERENDER_TO_PREFETCH_AND_USED:
        badge = this.#badgeSuccess();
        basicMessage = html`${i18nString(UIStrings.downgradedPrefetchUsed)}`;
        break;
      case UsedKind.PREFETCH_USED:
        badge = this.#badgeSuccess();
        basicMessage = html`${i18nString(UIStrings.prefetchUsed)}`;
        break;
      case UsedKind.PRERENDER_USED:
        badge = this.#badgeSuccess();
        basicMessage = html`${i18nString(UIStrings.prerenderUsed)}`;
        break;
      case UsedKind.PREFETCH_FAILED:
        badge = this.#badgeFailure();
        basicMessage = html`${i18nString(UIStrings.prefetchFailed)}`;
        break;
      case UsedKind.PRERENDER_FAILED:
        badge = this.#badgeFailure();
        basicMessage = html`${i18nString(UIStrings.prerenderFailed)}`;
        break;
      case UsedKind.NO_PRELOADS:
        badge = this.#badgeNeutral(i18nString(UIStrings.badgeNoSpeculativeLoads));
        basicMessage = html`${i18nString(UIStrings.noPreloads)}`;
        break;
    }

    let maybeFailureReasonMessage;
    if (kind === UsedKind.PREFETCH_FAILED) {
      assertNotNullOrUndefined(prefetch);
      maybeFailureReasonMessage = prefetchFailureReason(prefetch as SDK.PreloadingModel.PrefetchAttempt);
    } else if (kind === UsedKind.PRERENDER_FAILED || kind === UsedKind.DOWNGRADED_PRERENDER_TO_PREFETCH_AND_USED) {
      assertNotNullOrUndefined(prerender);
      maybeFailureReasonMessage = prerenderFailureReason(prerender as SDK.PreloadingModel.PrerenderAttempt);
    }

    let maybeFailureReason: Lit.LitTemplate = Lit.nothing;
    if (maybeFailureReasonMessage !== undefined) {
      // Disabled until https://crbug.com/1079231 is fixed.
      // clang-format off
      maybeFailureReason = html`
      <devtools-report-section-header>${i18nString(UIStrings.detailsFailureReason)}</devtools-report-section-header>
      <devtools-report-section>
        ${maybeFailureReasonMessage}
      </devtools-report-section>
      `;
      // clang-format on
    }

    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    return html`
      <devtools-report-section-header>${i18nString(UIStrings.speculativeLoadingStatusForThisPage)}</devtools-report-section-header>
      <devtools-report-section>
        <div>
          <div class="status-badge-container">
            ${badge}
          </div>
          <div>
            ${basicMessage}
          </div>
        </div>
      </devtools-report-section>

      ${maybeFailureReason}

      ${this.#maybeMismatchedSections(kind)}
      ${this.#maybeMismatchedHTTPHeadersSections()}
    `;
    // clang-format on
  }

  #maybeMismatchedSections(kind: UsedKind): Lit.LitTemplate {
    if (kind !== UsedKind.NO_PRELOADS || this.#data.previousAttempts.length === 0) {
      return Lit.nothing;
    }

    const rows = this.#data.previousAttempts.map(attempt => {
      return {
        url: attempt.key.url,
        action: attempt.key.action,
        status: attempt.status,
      };
    });
    const data = {
      pageURL: this.#data.pageURL,
      rows,
    };

    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    return html`
      <devtools-report-section-header>${i18nString(UIStrings.currentURL)}</devtools-report-section-header>
      <devtools-report-section>
        ${UI.XLink.XLink.create(this.#data.pageURL, undefined, 'link', undefined, 'current-url')}
      </devtools-report-section>

      <devtools-report-section-header>${i18nString(UIStrings.preloadedURLs)}</devtools-report-section-header>
      <devtools-report-section
      jslog=${VisualLogging.section('preloaded-urls')}>
        <devtools-resources-mismatched-preloading-grid
          .data=${data as MismatchedPreloadingGrid.MismatchedPreloadingGridData}></devtools-resources-mismatched-preloading-grid>
      </devtools-report-section>
    `;
    // clang-format on
  }

  #maybeMismatchedHTTPHeadersSections(): Lit.LitTemplate {
    const attempt = this.#data.previousAttempts.find(
        attempt =>
            attempt.action === Protocol.Preload.SpeculationAction.Prerender && attempt.mismatchedHeaders !== null);
    if (attempt === undefined) {
      return Lit.nothing;
    }

    if (attempt.key.url !== this.#data.pageURL) {
      // This place should never be reached since mismatched headers is reported only if the activation is attempted.
      // TODO(crbug.com/1456673): remove this check once DevTools support embedder-triggered prerender or prerender
      // supports non-vary-search.
      throw new Error('unreachable');
    }

    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    return html`
      <devtools-report-section-header>${i18nString(UIStrings.mismatchedHeadersDetail)}</devtools-report-section-header>
      <devtools-report-section>
        <devtools-resources-preloading-mismatched-headers-grid
          .data=${attempt as SDK.PreloadingModel.PrerenderAttempt}></devtools-resources-preloading-mismatched-headers-grid>
      </devtools-report-section>
    `;
    // clang-format on
  }

  #speculationsInitiatedByThisPageSummarySections(): Lit.LitTemplate {
    const count = this.#data.currentAttempts.reduce((acc, attempt) => {
      acc.set(attempt.status, (acc.get(attempt.status) ?? 0) + 1);
      return acc;
    }, new Map());
    const notTriggeredCount = count.get(SDK.PreloadingModel.PreloadingStatus.NOT_TRIGGERED) ?? 0;
    const readyCount = count.get(SDK.PreloadingModel.PreloadingStatus.READY) ?? 0;
    const failureCount = count.get(SDK.PreloadingModel.PreloadingStatus.FAILURE) ?? 0;
    const inProgressCount = (count.get(SDK.PreloadingModel.PreloadingStatus.PENDING) ?? 0) +
        (count.get(SDK.PreloadingModel.PreloadingStatus.RUNNING) ?? 0);
    const badges = [];
    if (this.#data.currentAttempts.length === 0) {
      badges.push(this.#badgeNeutral(i18nString(UIStrings.badgeNoSpeculativeLoads)));
    }
    if (notTriggeredCount > 0) {
      badges.push(this.#badgeNeutral(i18nString(UIStrings.badgeNotTriggeredWithCount, {n: notTriggeredCount})));
    }
    if (inProgressCount > 0) {
      badges.push(this.#badgeNeutral(i18nString(UIStrings.badgeInProgressWithCount, {n: inProgressCount})));
    }
    if (readyCount > 0) {
      badges.push(this.#badgeSuccess(readyCount));
    }
    if (failureCount > 0) {
      badges.push(this.#badgeFailure(failureCount));
    }

    const revealRuleSetView = (): void => {
      void Common.Revealer.reveal(new PreloadingHelper.PreloadingForward.RuleSetView(null));
    };
    const revealAttemptViewWithFilter = (): void => {
      void Common.Revealer.reveal(new PreloadingHelper.PreloadingForward.AttemptViewWithFilter(null));
    };

    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    return html`
      <devtools-report-section-header>${i18nString(UIStrings.speculationsInitiatedByThisPage)}</devtools-report-section-header>
      <devtools-report-section>
        <div>
          <div class="status-badge-container">
            ${badges}
          </div>

          <div class="reveal-links">
            <button class="link devtools-link" @click=${revealRuleSetView}
            jslog=${VisualLogging.action('view-all-rules').track({click: true})}>
              ${i18nString(UIStrings.viewAllRules)}
            </button>
           ・
            <button class="link devtools-link" @click=${revealAttemptViewWithFilter}
            jslog=${VisualLogging.action('view-all-speculations').track({click: true})}>
             ${i18nString(UIStrings.viewAllSpeculations)}
            </button>
          </div>
        </div>
      </devtools-report-section>
    `;
    // clang-format on
  }

  #badgeSuccess(count?: number): Lit.LitTemplate {
    let message;
    if (count === undefined) {
      message = i18nString(UIStrings.badgeSuccess);
    } else {
      message = i18nString(UIStrings.badgeSuccessWithCount, {n: count});
    }
    return this.#badge('status-badge status-badge-success', 'check-circle', message);
  }

  #badgeFailure(count?: number): Lit.LitTemplate {
    let message;
    if (count === undefined) {
      message = i18nString(UIStrings.badgeFailure);
    } else {
      message = i18nString(UIStrings.badgeFailureWithCount, {n: count});
    }
    return this.#badge('status-badge status-badge-failure', 'cross-circle', message);
  }

  #badgeNeutral(message: string): Lit.LitTemplate {
    return this.#badge('status-badge status-badge-neutral', 'clear', message);
  }

  #badge(klass: string, iconName: string, message: string): Lit.LitTemplate {
    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    return html`
      <span class=${klass}>
        <devtools-icon name=${iconName}></devtools-icon>
        <span>
          ${message}
        </span>
      </span>
    `;
    // clang-format on
  }
}

customElements.define('devtools-resources-used-preloading-view', UsedPreloadingView);

declare global {
  interface HTMLElementTagNameMap {
    'devtools-resources-used-preloading-view': UsedPreloadingView;
  }
}
