// 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 m from 'mithril';
import {duration, time} from '../base/time';
import {Size2D, VerticalBounds} from '../base/geom';
import {TimeScale} from '../base/time_scale';
import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
import {TrackEventDetailsPanel} from './details_panel';
import {TrackEventDetails, TrackEventSelection} from './selection';
import {SourceDataset} from '../trace_processor/dataset';
import {TrackNode} from './workspace';
import {CanvasColors} from './canvas_colors';
import {z} from 'zod';
import {Renderer} from '../base/renderer';

/**
 * Represents a snap point for the snap-to-boundaries feature.
 * When dragging selection boundaries, the cursor can snap to these points
 * to enable precise measurement of time intervals.
 */
export interface SnapPoint {
  /** The timestamp to snap to */
  time: time;
}

export interface TrackFilterCriteria {
  readonly name: string;

  // Run on each node to work out whether it satisfies the selected filter
  // option.
  readonly predicate: (track: TrackNode, filterOption: string) => boolean;

  // The list of possible filter options.
  readonly options: ReadonlyArray<{key: string; label: string}>;
}

export interface TrackManager {
  /**
   * Register a new track against a unique key known as a URI. The track is not
   * shown by default and callers need to either manually add it to a
   * Workspace or use registerTrackAndShowOnTraceLoad() below.
   */
  registerTrack(track: Track): void;

  findTrack(
    predicate: (track: Track) => boolean | undefined,
  ): Track | undefined;

  getAllTracks(): Track[];

  getTrack(uri: string): Track | undefined;

  /**
   * Register a track filter criteria, which can be used by end users to control
   * the list of tracks they see in workspaces. These criteria can provide more
   * power to the user compared to e.g. purely filtering by name.
   */
  registerTrackFilterCriteria(filter: TrackFilterCriteria): void;

  /**
   * Register a timeline overlay renderer.
   *
   * Overlays are rendered on top of all tracks in the timeline view and can be
   * used to draw annotations that span multiple tracks, such as flow arrows or
   * vertical lines marking specific events.
   */
  registerOverlay(overlay: Overlay): void;
}

export interface TrackContext {
  // This track's URI, used for making selections et al.
  readonly trackUri: string;

  // The TrackNode associated with this track, providing access to the track
  // hierarchy and children.
  readonly trackNode: TrackNode;
}

/**
 * Contextual information about the track passed to track lifecycle hooks &
 * render hooks with additional information about the timeline/canvas.
 */
export interface TrackRenderContext extends TrackContext {
  /**
   * The time span of the visible window.
   */
  readonly visibleWindow: HighPrecisionTimeSpan;

  /**
   * The dimensions of the track on the canvas in pixels.
   */
  readonly size: Size2D;

  /**
   * Suggested data resolution.
   *
   * This number is the number of time units that corresponds to 1 pixel on the
   * screen, rounded down to the nearest power of 2. The minimum value is 1.
   *
   * It's up to the track whether it would like to use this resolution or
   * calculate their own based on the timespan and the track dimensions.
   */
  readonly resolution: duration;

  /**
   * Canvas context used for rendering.
   */
  readonly ctx: CanvasRenderingContext2D;

  /**
   * A time scale used for translating between pixels and time.
   */
  readonly timescale: TimeScale;

  /**
   * Semantic colors which can vary depending on the current theme.
   */
  readonly colors: CanvasColors;

  /**
   * A high-performance renderer for drawing rectangles and billboards.
   * Uses WebGL when available, with Canvas 2D fallback.
   */
  readonly renderer: Renderer;
}

// A definition of a track, including a renderer implementation and metadata.
export interface Track {
  // A unique identifier for this track.
  readonly uri: string;

  // Describes how to render the track.
  readonly renderer: TrackRenderer;

  // Optional: A human readable description of the track. This can be a simple
  // string or a render function that returns Mithril vnodes.
  readonly description?: string | (() => m.Children);

  // Optional: Human readable subtitle. Sometimes displayed if there is room.
  readonly subtitle?: string;

  // Optional: A list of tags which provide additional metadata about the track.
  // Used mainly for legacy purposes that predate dataset.
  readonly tags?: TrackTags;

  // Optional: A list of strings which are displayed as "chips" in the track
  // shell.
  readonly chips?: ReadonlyArray<string>;

  // Filled in by the core.
  readonly pluginId?: string;
}

/**
 * Contextual information passed to mouse events.
 */
export interface TrackMouseEvent {
  /**
   * X coordinate of the mouse event w.r.t. the top-left of the track.
   */
  readonly x: number;

  /**
   * Y coordinate of the mouse event w.r.t the top-left of the track.
   */
  readonly y: number;

  /**
   * A time scale used for translating between pixels and time.
   */
  readonly timescale: TimeScale;
}

/**
 * A descriptor for a track setting, which describes the setting's metadata and
 * how to render a control for it. This is separate from the track setting
 * itself because multiple tracks (which will need different TrackSetting
 * instances) can share the same descriptor, which would make them editable in
 * bulk.
 *
 * A lot of the fields in this interface are currently unused, but they will be
 * used in the future when track serialization is implemented.
 */
export interface TrackSettingDescriptor<T> {
  // A unique identifier for this setting. Will be used to store the serialized
  // value for this setting. Currently unused.
  readonly id: string;

  // A human readable name for this setting. This is displayed in the settings
  // menu unless overridden.
  readonly name: string;

  // A human readable description for this setting. Currently unused, but good
  // practice to require this in order to document what a setting does and is
  // used for.
  readonly description: string;

  // A Zod schema describing the setting's value type which is used to infer the
  // automatic settings menu type and options, and will be used for
  // serialization and deserialization.
  readonly schema: z.ZodType<T>;

  // The default value for this setting. This will be used to render a 'reset'
  // button in the render menu, and possibly as a fallback if parsing fails when
  // we add serialization. Currently unused.
  readonly defaultValue: T;

  // An optional function used to render a control for this setting. This
  // describes what the control looks like in the settings menu on the track and
  // also the bulk settings menu when multiple tracks are selected. If omitted,
  // a control will be automatically generated based on the schema and name.
  render?(setter: (value: T) => void, values: ReadonlyArray<T>): m.Children;
}

/**
 * A setting that can be changed by the user that affects how the track is
 * rendered or behaves. References a TrackSettingDescriptor which describes the
 * setting's metadata and how to render a control for it.
 */
export interface TrackSetting<T> {
  readonly descriptor: TrackSettingDescriptor<T>;
  getValue: () => T;
  setValue(newValue: T): void;
}

export interface TrackRenderer {
  /**
   * Describes which root table the events on this track come from. This is
   * mainly for use by flows (before they get refactored to be more generic) and
   * will be used by the SQL table resolver mechanism along with dataset.
   * TODO(stevegolton): Maybe move this onto dataset directly?
   */
  readonly rootTableName?: string;

  /**
   * An optional list of settings that can be modified externally to affect how
   * the track is rendered or behaves.
   *
   * These settings are user-editable and are shown in the track settings menu.
   * They can also be modified in bulk if multiple tracks share the same setting
   * descriptor.
   *
   * Note: The core does not yet provide a mechanism to persist these settings
   * to permalinks.
   */
  readonly settings?: ReadonlyArray<TrackSetting<unknown>>;

  /**
   * Required method used to render the track's content to the canvas, called
   * synchronously on every render cycle.
   *
   * Tracks can use ctx (Canvas 2D) for text and shapes, and canvasRenderer
   * (WebGL) for high-performance rectangle rendering. Both are available
   * in the same render call, and the WebGL content will appear behind
   * Canvas 2D content.
   */
  render(ctx: TrackRenderContext): void;

  /**
   * Return the vertical bounds (top & bottom) of a slice were it to be rendered
   * at a specific depth, given the slice height and padding/spacing that this
   * track uses.
   */
  getSliceVerticalBounds?(depth: number): VerticalBounds | undefined;
  getHeight?(): number;
  getTrackShellButtons?(): m.Children;
  onMouseMove?(event: TrackMouseEvent): void;
  onMouseClick?(event: TrackMouseEvent): boolean;
  onMouseOut?(): void;

  /**
   * Optional: Returns a dataset that represents the events displayed on this
   * track.
   */
  getDataset?(): SourceDataset | undefined;

  /**
   * Optional: Get details of a track event given by eventId on this track.
   */
  getSelectionDetails?(eventId: number): Promise<TrackEventDetails | undefined>;

  // Optional: A factory that returns a details panel object for a given track
  // event selection. This is called each time the selection is changed (and the
  // selection is relevant to this track).
  detailsPanel?(sel: TrackEventSelection): TrackEventDetailsPanel | undefined;

  // Optional: Returns tooltip content if available. If the return value is
  // falsy, no tooltip is rendered.
  renderTooltip?(): m.Children;

  /**
   * Optional: Find the nearest snap point to the given time.
   * Returns undefined if no snap point is within threshold.
   *
   * This method is used by the snap-to-boundaries feature to enable precise
   * measurement of time intervals. When dragging selection boundaries, the
   * cursor can snap to these points.
   *
   * @param targetTime - Target time to snap from
   * @param thresholdPx - Maximum pixel distance to snap
   * @param timescale - For converting between time and pixels
   * @returns The nearest snap point, or undefined if none within threshold
   */
  getSnapPoint?(
    targetTime: time,
    thresholdPx: number,
    timescale: TimeScale,
  ): SnapPoint | undefined;
}

// An set of key/value pairs describing a given track. These are used for
// selecting tracks to pin/unpin, diplsaying "chips" in the track shell, and
// (in future) the sorting and grouping of tracks.
// We define a handful of well known fields, and the rest are arbitrary key-
// value pairs.
export type TrackTags = Partial<WellKnownTrackTags> & {
  // There may be arbitrary other key/value pairs.
  [key: string]:
    | undefined
    | string
    | number
    | boolean
    | ReadonlyArray<string>
    | ReadonlyArray<number>;
};

interface WellKnownTrackTags {
  // The track "kinds", are by various subsystems e.g. aggregation controllers
  // in order to select tracks to operate on. A good analogy is how CSS
  // selectors can match elements using their class list.
  kinds: ReadonlyArray<string>;

  // Optional: list of track IDs represented by this trace.
  // This list is used for participation in track indexing by track ID.
  // This index is used by various subsystems to find links between tracks based
  // on the track IDs used by trace processor.
  trackIds: ReadonlyArray<number>;

  // Optional: The CPU number associated with this track.
  cpu: number;

  // Optional: The UTID associated with this track.
  utid: number;

  // Optional: The UPID associated with this track.
  upid: number;

  // Track type, used for filtering
  type: string;
}

/**
 * Contains a track and it's top and bottom coordinates in the timeline.
 */
export interface TrackBounds {
  readonly node: TrackNode;
  readonly verticalBounds: VerticalBounds;
}

export interface Overlay {
  render(
    ctx: CanvasRenderingContext2D,
    timescale: TimeScale,
    size: Size2D,
    tracks: ReadonlyArray<TrackBounds>,
    theme: CanvasColors,
  ): void;
}
