// Copyright (C) 2025 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 {sqliteString} from '../base/string_utils';
import {exists} from '../base/utils';
import {
  ANDROID_LOGS_TRACK_KIND,
  CPU_SLICE_TRACK_KIND,
} from '../public/track_kinds';
import type {Engine} from '../trace_processor/engine';
import {LONG, NUM, STR} from '../trace_processor/query_result';
import {escapeSearchQuery} from '../trace_processor/query_utils';
import type {CurrentSearchResults, SearchSource} from './search_data';
import type {TrackManagerImpl} from './track_manager';

/**
 * Executes a SQL-based search across the trace data.
 *
 * This function searches for matches in:
 * - Thread and process names (via sched table)
 * - Slice names and arguments
 * - Android log messages
 *
 * @param engine - The trace processor engine
 * @param trackManager - The track manager to resolve track URIs
 * @param searchText - The search term
 * @returns Search results containing matching events
 */
export async function executeSqlSearch(
  engine: Engine,
  trackManager: TrackManagerImpl,
  searchText: string,
): Promise<CurrentSearchResults> {
  // Build indexes for track lookups
  const trackUrisByCpu = new Map<number, string>();
  const allTracks = trackManager.getAllTracks();
  allTracks.forEach((td) => {
    const tags = td?.tags;
    const cpu = tags?.cpu;
    const kinds = tags?.kinds;
    exists(cpu) &&
      kinds?.includes(CPU_SLICE_TRACK_KIND) &&
      trackUrisByCpu.set(cpu, td.uri);
  });

  const trackUrisByTrackId = new Map<number, string>();
  allTracks.forEach((td) => {
    const trackIds = td?.tags?.trackIds ?? [];
    trackIds.forEach((trackId) => trackUrisByTrackId.set(trackId, td.uri));
  });

  const searchLiteral = escapeSearchQuery(searchText);

  // Find matching threads/processes
  const utidRes = await engine.query(`select utid from thread join process
    using(upid) where
      thread.name GLOB ${searchLiteral} or
      process.name GLOB ${searchLiteral}`);
  const utids = [];
  for (const it = utidRes.iter({utid: NUM}); it.valid(); it.next()) {
    utids.push(it.utid);
  }

  // Execute main search query
  const res = await engine.query(`
    select
      id as sliceId,
      ts,
      'cpu' as source,
      cpu as sourceId,
      utid
    from sched where utid in (${utids.join(',')})
    union all
    select *
    from (
      select
        slice_id as sliceId,
        ts,
        'slice' as source,
        track_id as sourceId,
        0 as utid
        from slice
        where slice.name GLOB ${searchLiteral}
          or (
            0 != CAST(${sqliteString(searchText)} AS INT) and
            sliceId = CAST(${sqliteString(searchText)} AS INT)
          )
      union
      select
        slice_id as sliceId,
        ts,
        'slice' as source,
        track_id as sourceId,
        0 as utid
      from slice
      join args using(arg_set_id)
      where string_value GLOB ${searchLiteral} or key GLOB ${searchLiteral}
    )
    union all
    select
      id as sliceId,
      ts,
      'log' as source,
      0 as sourceId,
      utid
    from android_logs where msg GLOB ${searchLiteral}
    order by ts
  `);

  // Process results
  const searchResults: CurrentSearchResults = {
    eventIds: new Float64Array(0),
    tses: new BigInt64Array(0),
    utids: new Float64Array(0),
    sources: [],
    trackUris: [],
    totalResults: 0,
  };

  const rows = res.numRows();
  searchResults.eventIds = new Float64Array(rows);
  searchResults.tses = new BigInt64Array(rows);
  searchResults.utids = new Float64Array(rows);

  const it = res.iter({
    sliceId: NUM,
    ts: LONG,
    source: STR,
    sourceId: NUM,
    utid: NUM,
  });
  for (; it.valid(); it.next()) {
    let track: string | undefined = undefined;

    if (it.source === 'cpu') {
      track = trackUrisByCpu.get(it.sourceId);
    } else if (it.source === 'slice') {
      track = trackUrisByTrackId.get(it.sourceId);
    } else if (it.source === 'log') {
      track = trackManager
        .getAllTracks()
        .find((td) => td.tags?.kinds?.includes(ANDROID_LOGS_TRACK_KIND))?.uri;
    }
    // The .get() calls above could return undefined, this isn't just an else.
    if (track === undefined) {
      continue;
    }

    const i = searchResults.totalResults++;
    searchResults.trackUris.push(track);
    searchResults.sources.push(it.source as SearchSource);
    searchResults.eventIds[i] = it.sliceId;
    searchResults.tses[i] = it.ts;
    searchResults.utids[i] = it.utid;
  }

  return searchResults;
}
