// Copyright 2014 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 SDK from '../../core/sdk/sdk.js';
import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
import * as Protocol from '../../generated/protocol.js';
import type * as Trace from '../../models/trace/trace.js';

export class TracingManager extends SDK.SDKModel.SDKModel<void> {
  readonly #tracingAgent: ProtocolProxyApi.TracingApi;
  #activeClient: TracingManagerClient|null;
  #eventsRetrieved: number;
  #finishing?: boolean;
  constructor(target: SDK.Target.Target) {
    super(target);
    this.#tracingAgent = target.tracingAgent();
    target.registerTracingDispatcher(new TracingDispatcher(this));

    this.#activeClient = null;
    this.#eventsRetrieved = 0;
  }

  bufferUsage(usage?: number, percentFull?: number): void {
    if (this.#activeClient) {
      this.#activeClient.tracingBufferUsage(usage || percentFull || 0);
    }
  }

  eventsCollected(events: Trace.Types.Events.Event[]): void {
    if (!this.#activeClient) {
      return;
    }
    this.#activeClient.traceEventsCollected(events);
    this.#eventsRetrieved += events.length;

    // CDP no longer provides an approximate_event_count AKA eventCount. It's always 0.
    // To give some idea of progress we'll compare to a large (900k event) trace.
    // And we'll clamp both sides so the user sees some progress, and never maxed at 99%
    const progress = Math.min((this.#eventsRetrieved / 900_000) + 0.15, 0.90);
    this.#activeClient.eventsRetrievalProgress(progress);
  }

  tracingComplete(): void {
    this.#eventsRetrieved = 0;
    if (this.#activeClient) {
      this.#activeClient.tracingComplete();
      this.#activeClient = null;
    }
    this.#finishing = false;
  }

  async reset(): Promise<void> {
    // If we have an active client, we should try to stop
    // it before resetting it, else we will leave the
    // backend in a broken state where it thinks we are in
    // the middle of tracing, but we think we are not.
    // Then, any subsequent attempts to record will fail
    // because the backend will not let us start a second
    // tracing session.
    if (this.#activeClient) {
      await this.#tracingAgent.invoke_end();
    }
    this.#eventsRetrieved = 0;
    this.#activeClient = null;
    this.#finishing = false;
  }

  async start(client: TracingManagerClient, categoryFilter: string): Promise<Protocol.ProtocolResponseWithError> {
    if (this.#activeClient) {
      throw new Error('Tracing is already started');
    }
    const bufferUsageReportingIntervalMs = 500;
    this.#activeClient = client;
    const args = {
      bufferUsageReportingInterval: bufferUsageReportingIntervalMs,
      transferMode: Protocol.Tracing.StartRequestTransferMode.ReportEvents,
      traceConfig: {
        recordMode: Protocol.Tracing.TraceConfigRecordMode.RecordUntilFull,
        traceBufferSizeInKb: 1200 * 1000,
        includedCategories: categoryFilter.split(','),
      },
    };
    const response = await this.#tracingAgent.invoke_start(args);
    if (response.getError()) {
      this.#activeClient = null;
    }
    return response;
  }

  stop(): void {
    if (!this.#activeClient) {
      throw new Error('Tracing is not started');
    }
    if (this.#finishing) {
      throw new Error('Tracing is already being stopped');
    }
    this.#finishing = true;
    void this.#tracingAgent.invoke_end();
  }
}

export interface TracingManagerClient {
  traceEventsCollected(events: Trace.Types.Events.Event[]): void;

  tracingComplete(): void;
  tracingBufferUsage(usage: number): void;
  eventsRetrievalProgress(progress: number): void;
}

class TracingDispatcher implements ProtocolProxyApi.TracingDispatcher {
  readonly #tracingManager: TracingManager;
  constructor(tracingManager: TracingManager) {
    this.#tracingManager = tracingManager;
  }

  // `eventCount` will always be 0 as perfetto no longer calculates `approximate_event_count`
  bufferUsage({value, percentFull}: Protocol.Tracing.BufferUsageEvent): void {
    this.#tracingManager.bufferUsage(value, percentFull);
  }

  dataCollected({value}: Protocol.Tracing.DataCollectedEvent): void {
    this.#tracingManager.eventsCollected(value);
  }

  tracingComplete(): void {
    this.#tracingManager.tracingComplete();
  }
}

SDK.SDKModel.SDKModel.register(TracingManager, {capabilities: SDK.Target.Capability.TRACING, autostart: false});
