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

import '../../ui/legacy/legacy.js';

import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
import type * as Trace from '../../models/trace/trace.js';
import * as Workspace from '../../models/workspace/workspace.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as UI from '../../ui/legacy/legacy.js';
import {html, nothing, render} from '../../ui/lit/lit.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';

import {traceJsonGenerator} from './SaveFileFormatter.js';
import timelineStatusDialogStyles from './timelineStatusDialog.css.js';

const UIStrings = {
  /**
   * @description Text to download the trace file after an error
   */
  downloadAfterError: 'Download trace',
  /**
   * @description Text for the status of something
   */
  status: 'Status',
  /**
   * @description Text that refers to the time
   */
  time: 'Time',
  /**
   * @description Text for the description of something
   */
  description: 'Description',
  /**
   * @description Text of an item that stops the running task
   */
  stop: 'Stop',

} as const;
const str_ = i18n.i18n.registerUIStrings('panels/timeline/StatusDialog.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

export interface ViewInput {
  statusText: string;
  showTimer: boolean;
  timeText: string;
  showProgress: boolean;
  progressActivity: string;
  progressPercent: number;
  descriptionText: string|undefined;
  buttonText: string;
  hideStopButton: boolean;
  focusStopButton: boolean;
  showDownloadButton: boolean;
  downloadButtonDisabled: boolean;
  onStopClick: () => void;
  onDownloadClick: () => void;
}

export type ViewOutput = Record<string, never>;

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

// clang-format off
export const DEFAULT_VIEW: View = (input, output, target) => {
  render(html`
    <style>${timelineStatusDialogStyles}</style>
    <div class="timeline-status-dialog">
      <div class="status-dialog-line status">
        <div class="label">${i18nString(UIStrings.status)}</div>
        <div class="content" role="status">${input.statusText}</div>
      </div>
      ${input.showTimer ? html`
        <div class="status-dialog-line time">
          <div class="label">${i18nString(UIStrings.time)}</div>
          <div class="content">${input.timeText}</div>
        </div>
      ` : nothing}
      ${input.showProgress ? html`
        <div class="status-dialog-line progress">
          <div class="label">${input.progressActivity}</div>
          <div class="indicator-container">
            <div class="indicator"
              style="width: ${input.progressPercent.toFixed(1)}%"
              role="progressbar"
              aria-valuemin="0"
              aria-valuemax="100"
              aria-valuenow=${input.progressPercent}>
            </div>
          </div>
        </div>
      ` : nothing}
      ${input.descriptionText !== undefined ? html`
        <div class="status-dialog-line description">
          <div class="label">${i18nString(UIStrings.description)}</div>
          <div class="content">${input.descriptionText}</div>
        </div>
      ` : nothing}
      <div class="stop-button">
        ${input.showDownloadButton ? html`
          <devtools-button
            .variant=${Buttons.Button.Variant.OUTLINED}
            .disabled=${input.downloadButtonDisabled}
            @click=${input.onDownloadClick}
            .jslogContext=${'timeline.download-after-error'}
          >${i18nString(UIStrings.downloadAfterError)}</devtools-button>
        ` : nothing}
        ${!input.hideStopButton ? html`
          <devtools-button
            .variant=${Buttons.Button.Variant.PRIMARY}
            @click=${input.onStopClick}
            .jslogContext=${'timeline.stop-recording'}
            ?autofocus=${input.focusStopButton}
          >${input.buttonText}</devtools-button>
        ` : nothing}
      </div>
    </div>
  `, target);
};
// clang-format on

/**
 * This is the dialog shown whilst a trace is being recorded/imported.
 */
export class StatusDialog extends UI.Widget.VBox {
  readonly #view: View;
  #statusText = '';
  readonly #showTimer: boolean;
  #timeText = '';
  readonly #showProgress: boolean;
  #progressActivity = '';
  #progressPercent = 0;
  readonly #descriptionText: string|undefined;
  readonly #buttonText: string;
  #hideStopButton: boolean;
  #focusStopButton = false;
  #showDownloadButton = false;
  #downloadButtonDisabled = true;
  readonly #onButtonClickCallback: () => (Promise<void>| void);
  #startTime!: number;
  #timeUpdateTimer?: number;
  #rawEvents?: Trace.Types.Events.Event[];

  constructor(
      options: {
        hideStopButton: boolean,
        showTimer?: boolean,
        showProgress?: boolean,
        description?: string,
        buttonText?: string,
      },
      onButtonClickCallback: () => (Promise<void>| void), view: View = DEFAULT_VIEW) {
    super({
      jslog: `${VisualLogging.dialog('timeline-status').track({resize: true})}`,
      useShadowDom: true,
    });

    this.#view = view;
    this.#showTimer = Boolean(options.showTimer);
    this.#showProgress = Boolean(options.showProgress);
    this.#descriptionText = options.description;
    this.#buttonText = options.buttonText || i18nString(UIStrings.stop);
    this.#hideStopButton = options.hideStopButton;
    this.#onButtonClickCallback = onButtonClickCallback;
  }

  finish(): void {
    this.stopTimer();
    this.#hideStopButton = true;
    this.requestUpdate();
  }

  async #downloadRawTraceAfterError(): Promise<void> {
    if (!this.#rawEvents || this.#rawEvents.length === 0) {
      return;
    }
    const traceStart = Platform.DateUtilities.toISO8601Compact(new Date());
    const fileName = `Trace-Load-Error-${traceStart}.json` as Platform.DevToolsPath.RawPathString;
    const formattedTraceIter = traceJsonGenerator(this.#rawEvents, {});
    const traceAsString = Array.from(formattedTraceIter).join('');
    await Workspace.FileManager.FileManager.instance().save(
        fileName, new TextUtils.ContentData.ContentData(traceAsString, /* isBase64=*/ false, 'application/json'),
        /* forceSaveAs=*/ true);
    Workspace.FileManager.FileManager.instance().close(fileName);
  }

  enableDownloadOfEvents(rawEvents: Trace.Types.Events.Event[]): void {
    this.#rawEvents = rawEvents;
    this.#showDownloadButton = true;
    this.#downloadButtonDisabled = false;
    this.requestUpdate();
  }

  remove(): void {
    (this.element.parentNode as HTMLElement)?.classList.remove('opaque', 'tinted');
    this.stopTimer();
    this.element.remove();
  }

  showPane(parent: Element, mode: 'tinted'|'opaque' = 'opaque'): void {
    this.show(parent);
    parent.classList.toggle('tinted', mode === 'tinted');
    parent.classList.toggle('opaque', mode === 'opaque');
  }

  enableAndFocusButton(): void {
    this.#hideStopButton = false;
    this.#focusStopButton = true;
    this.requestUpdate();
  }

  updateStatus(text: string): void {
    this.#statusText = text;
    this.requestUpdate();
  }

  updateProgressBar(activity: string, percent: number): void {
    this.#progressActivity = activity;
    this.#progressPercent = percent;
    this.#updateTimerTick();
    this.requestUpdate();
  }

  startTimer(): void {
    this.#startTime = Date.now();
    this.#timeUpdateTimer = window.setInterval(this.#updateTimerTick.bind(this), 100);
    this.#updateTimerTick();
  }

  private stopTimer(): void {
    if (!this.#timeUpdateTimer) {
      return;
    }
    clearInterval(this.#timeUpdateTimer);
    this.#updateTimerTick();
    this.#timeUpdateTimer = undefined;
  }

  #updateTimerTick(): void {
    if (!this.#timeUpdateTimer || !this.#showTimer) {
      return;
    }
    const seconds = (Date.now() - this.#startTime) / 1000;
    this.#timeText = i18n.TimeUtilities.preciseSecondsToString(seconds, 1);
    this.requestUpdate();
  }

  override performUpdate(): void {
    this.#view(
        {
          statusText: this.#statusText,
          showTimer: this.#showTimer,
          timeText: this.#timeText,
          showProgress: this.#showProgress,
          progressActivity: this.#progressActivity,
          progressPercent: this.#progressPercent,
          descriptionText: this.#descriptionText,
          buttonText: this.#buttonText,
          hideStopButton: this.#hideStopButton,
          focusStopButton: this.#focusStopButton,
          showDownloadButton: this.#showDownloadButton,
          downloadButtonDisabled: this.#downloadButtonDisabled,
          onStopClick: () => {
            void this.#onButtonClickCallback();
          },
          onDownloadClick: () => {
            void this.#downloadRawTraceAfterError();
          },
        },
        {},
        this.contentElement,
    );
    this.#focusStopButton = false;
  }

  override wasShown(): void {
    super.wasShown();
    this.requestUpdate();
  }
}
