// 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 type m from 'mithril';
import type {SqlValue} from '../../../../trace_processor/query_result';
import type {Trace} from '../../../../public/trace';
import {type SqlColumn, sqlColumnId} from './sql_column';
import type {Filters} from './filters';
import type {PerfettoSqlType} from '../../../../trace_processor/perfetto_sql_type';

// Interface which allows TableColumn to interact with the table (e.g. add filters, or run the query).
export interface TableManager {
  filters: Filters;
  trace: Trace;
  getSqlQuery(data: {[key: string]: SqlColumn}): string;
}

// Context passed to renderCell to allow interaction with the table.
export interface RenderCellContext {
  filters: Filters;
  trace: Trace;
  getSqlQuery(data: {[key: string]: SqlColumn}): string;
  hasColumn(column: TableColumn): boolean;
  addColumn(column: TableColumn): void;
}

// Context passed to listDerivedColumns to provide information about the table.
export interface ListColumnsContext {
  filters: Filters;
  trace: Trace;
  getSqlQuery(data: {[key: string]: SqlColumn}): string;
}

export interface TableColumnParams {
  // See TableColumn.tag.
  tag?: string;
  // See TableColumn.alias.
  alias?: string;
  // See TableColumn.startsHidden.
  startsHidden?: boolean;
}

// Class which represents a column in a table, which can be displayed to the user.
export interface TableColumn {
  readonly column: SqlColumn;
  readonly type: PerfettoSqlType | undefined;
  // In some cases, the UI needs additional information to be able to render a given cell (e.g. for display arg values,
  // we need to know arg type as well as arg value to generate a correct filter). In these cases, the common solution is fetch a JSON value
  // or a protobuf from the SQL and render it accordingly, so if set, `display` column overrides which value is going to be passed to `renderCell`.
  // `column` is still always going to be used for sorting, aggregation and casting.
  readonly display?: SqlColumn;

  // Column title to be displayed.
  // If not set, then `alias` will be used if it's unique.
  // If `alias` is not set as well, then `sqlColumnId(primaryColumn())` will be used.
  // TODO(altimin): This should return m.Children, but a bunch of things, including low-level widgets (Button, MenuItem, Anchor) need to be fixed first.
  getTitle?(): string | undefined;

  // Get column-specific menu items for this column.
  // This allows columns to provide their own menu items in the table header menu.
  // For example, a CastColumn can provide an "Undo cast" menu item.
  getColumnSpecificMenuItems?(args: {
    replaceColumn: (column: TableColumn) => void;
  }): m.Children;

  /**
   * Render a table cell. context can be undefined, in which case the cell should provide basic rendering (e.g. for pivot table).
   *
   * @param value The value to be rendered.
   * @param context Optional context to allow interaction with the table (e.g. adding filters).
   */
  renderCell(value: SqlValue, context?: RenderCellContext): RenderedCell;

  // A set of columns to be added when opening this table.
  // It has two primary purposes:
  // - Allow some columns to be hidden by default (by returning an empty array).
  // - Expand some columns (e.g. utid and upid are not meaningful by themselves, so the corresponding columns might add a "name" column by default).
  initialColumns?(): TableColumn[];

  // Some columns / values (arg_set_ids, table ids, etc) are primarily used to reference other data.
  // This method allows showing the user list of additional columns which can be fetched using this column.
  listDerivedColumns?(
    context: ListColumnsContext,
  ): undefined | (() => Promise<Map<string, TableColumn>>);
}

// Returns a unique identifier for the table column.
export function tableColumnId(column: TableColumn): string {
  return sqlColumnId(column.column);
}

export function tableColumnAlias(column: TableColumn): string {
  return tableColumnId(column).replace(/[^a-zA-Z0-9_]/g, (char) => {
    if (char === '_') {
      return '__';
    }
    return '_' + char.charCodeAt(0);
  });
}

export function columnTitle(column: TableColumn): string {
  if (column.getTitle !== undefined) {
    const title = column.getTitle();
    if (title !== undefined) return title;
  }
  return sqlColumnId(column.column);
}

export interface RenderedCell {
  readonly content: m.Children;
  readonly menu?: m.Children;
  readonly isNumerical?: boolean;
  readonly isNull?: boolean;
}
