// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {
  expandProcessName,
  type CujScopedMetricData,
  type MetricHandler,
  type JankType,
} from './metricUtils';
import {NUM} from '../../../trace_processor/query_result';
import type {Trace} from '../../../public/trace';

// TODO(primiano): make deps check stricter, we shouldn't allow plugins to
// depend on each other.
import {focusOnSlice} from '../../com.android.AndroidCujs/trackUtils';
import {addDebugSliceTrack} from '../../../components/tracks/debug_tracks';

const ENABLE_FOCUS_ON_FIRST_JANK = true;

class PinCujScopedJank implements MetricHandler {
  /**
   * Matches metric key & return parsed data if successful.
   *
   * @param {string} metricKey The metric key to match.
   * @returns {CujScopedMetricData | undefined} Parsed data or undefined if no match.
   */
  public match(metricKey: string): CujScopedMetricData | undefined {
    const matcher =
      /perfetto_cuj_(?<process>.*)-(?<cujName>.*)-.*-(?<jps>weighted_)?missed_(?<jankType>frames|sf_frames|app_frames)/;
    const match = matcher.exec(metricKey);
    if (!match?.groups) {
      return undefined;
    }
    return {
      process: expandProcessName(match.groups.process),
      cujName: match.groups.cujName,
      jankType: match.groups.jankType as JankType,
      isWeighted: !!match.groups.jps,
    };
  }

  /**
   * Adds the debug tracks for cuj Scoped jank metrics.
   *
   * @param {CujScopedMetricData} metricData Parsed metric data for the cuj scoped jank
   * @param {Trace} ctx PluginContextTrace for trace related properties and methods
   * @returns {void} Adds one track for Jank CUJ slice and one for Janky CUJ frames
   */
  public async addMetricTrack(metricData: CujScopedMetricData, ctx: Trace) {
    // TODO: b/349502258 - Refactor to single API
    const {tableName, ...config} = await this.cujScopedTrackConfig(
      metricData,
      ctx,
    );
    addDebugSliceTrack({trace: ctx, ...config});
    if (ENABLE_FOCUS_ON_FIRST_JANK) {
      await this.focusOnFirstJank(ctx, tableName);
    }
  }

  private async cujScopedTrackConfig(
    metricData: CujScopedMetricData,
    ctx: Trace,
  ) {
    let jankTypeFilter = 'AND (app_missed > 0 OR sf_missed > 0)';
    let jankTypeDisplayName = 'all';
    if (metricData.jankType?.includes('app')) {
      jankTypeFilter = ' AND app_missed > 0';
      jankTypeDisplayName = 'app';
    } else if (metricData.jankType?.includes('sf')) {
      jankTypeFilter = ' AND sf_missed > 0';
      jankTypeDisplayName = 'sf';
    }
    if (metricData.isWeighted) {
      jankTypeFilter += ' AND jank_score > 0';
    }

    const cuj = metricData.cujName;
    const processName = metricData.process;

    const tableWithJankyFramesName = `_janky_frames_during_cuj_from_metric_key_${Math.floor(Math.random() * 1_000_000)}`;

    const createJankyCujFrameTable = `
      CREATE OR REPLACE PERFETTO TABLE ${tableWithJankyFramesName} AS
      SELECT
        f.vsync as id,
        f.ts AS ts,
        f.dur as dur,
        f.jank_score as jank_score
      FROM android_jank_cuj_frame f LEFT JOIN android_jank_cuj cuj USING (cuj_id)
      WHERE cuj.process_name = "${processName}"
      AND cuj_name = "${cuj}" ${jankTypeFilter}
    `;

    await ctx.engine.query(createJankyCujFrameTable);

    const jankyFramesDuringCujQuery = `
      SELECT id, ts, dur, jank_score
      FROM ${tableWithJankyFramesName}
    `;

    const trackName = jankTypeDisplayName + ' missed frames in ' + processName;

    const cujScopedJankSlice = {
      data: {
        sqlSource: jankyFramesDuringCujQuery,
        columns: ['id', 'ts', 'dur', 'jank_score'],
      },
      columns: {
        ts: 'ts',
        dur: 'dur',
        name: metricData.isWeighted ? 'jank_score' : 'id',
      },
      argColumns: ['id', 'ts', 'dur', 'jank_score'],
      title: trackName,
    };

    return {
      ...cujScopedJankSlice,
      tableName: tableWithJankyFramesName,
    };
  }

  private async focusOnFirstJank(ctx: Trace, tableWithJankyFramesName: string) {
    const queryForFirstJankyFrame = `
      SELECT id as slice_id, track_id
      FROM actual_frame_timeline_slice
      WHERE name = cast_string!(
        (SELECT id FROM ${tableWithJankyFramesName} LIMIT 1)
      );
    `;
    const queryResult = await ctx.engine.query(queryForFirstJankyFrame);
    if (queryResult.numRows() === 0) {
      return;
    }
    const row = queryResult.firstRow({
      slice_id: NUM,
      track_id: NUM,
    });
    focusOnSlice(ctx, row.slice_id);
  }
}

export const pinCujScopedJankInstance = new PinCujScopedJank();
