// Copyright (C) 2026 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 {Engine} from '../../../trace_processor/engine';
import {
  NUM,
  STR_NULL,
  type QueryResult,
} from '../../../trace_processor/query_result';
import {
  ChartSource,
  SQLChartLoader,
  type QueryConfig,
  type ChartLoaderResult,
  inFilter,
} from './chart_sql_source';
import type {ChartAggregation} from './chart_utils';
import type {SankeyData, SankeyNode, SankeyLink} from './sankey';

/**
 * Configuration for SQLSankeyLoader.
 */
export interface SQLSankeyLoaderOpts {
  /** The trace processor engine to run queries against. */
  readonly engine: Engine;

  /**
   * SQL query that provides the raw data.
   * Must include source, target, and value columns.
   */
  readonly query: string;

  /** Column name for the source node of each link. */
  readonly sourceColumn: string;

  /** Column name for the target node of each link. */
  readonly targetColumn: string;

  /** Column name for the link value (measure). */
  readonly valueColumn: string;
}

/**
 * Per-use configuration for the sankey loader.
 */
export interface SankeyLoaderConfig {
  /** Aggregation function to apply to the value column. Defaults to 'SUM'. */
  readonly aggregation?: ChartAggregation;

  /** Maximum number of links to return. Defaults to no limit. */
  readonly limit?: number;

  /** Filter to only include specific source nodes. */
  readonly sourceFilter?: ReadonlyArray<string | number>;

  /** Filter to only include specific target nodes. */
  readonly targetFilter?: ReadonlyArray<string | number>;
}

/** Result returned by the sankey loader. */
export type SankeyLoaderResult = ChartLoaderResult<SankeyData>;

/**
 * SQL-based sankey loader with async loading and caching.
 *
 * Builds a flat graph of nodes and weighted links from two dimensions
 * (source, target) and one measure (value). Uses QuerySlot for caching
 * and request deduplication.
 */
export class SQLSankeyLoader extends SQLChartLoader<
  SankeyLoaderConfig,
  SankeyData
> {
  private readonly sourceColumn: string;
  private readonly targetColumn: string;
  private readonly valueColumn: string;

  constructor(opts: SQLSankeyLoaderOpts) {
    super(
      opts.engine,
      new ChartSource({
        query: opts.query,
        schema: {
          [opts.sourceColumn]: 'text',
          [opts.targetColumn]: 'text',
          [opts.valueColumn]: 'real',
        },
      }),
    );
    this.sourceColumn = opts.sourceColumn;
    this.targetColumn = opts.targetColumn;
    this.valueColumn = opts.valueColumn;
  }

  protected buildQueryConfig(config: SankeyLoaderConfig): QueryConfig {
    const aggregation = config.aggregation ?? 'SUM';

    const filters = [
      ...inFilter(this.sourceColumn, config.sourceFilter),
      ...inFilter(this.targetColumn, config.targetFilter),
    ];

    return {
      type: 'aggregated',
      dimensions: [
        {column: this.sourceColumn, alias: '_source'},
        {column: this.targetColumn, alias: '_target'},
      ],
      measures: [{column: this.valueColumn, aggregation}],
      filters,
      limit: config.limit,
      orderDirection: 'desc',
    };
  }

  protected parseResult(queryResult: QueryResult): SankeyData {
    const nodeSet = new Set<string>();
    const links: SankeyLink[] = [];

    const iter = queryResult.iter({
      _source: STR_NULL,
      _target: STR_NULL,
      _value: NUM,
    });

    for (; iter.valid(); iter.next()) {
      const source = iter._source ?? '(null)';
      const target = iter._target ?? '(null)';
      nodeSet.add(source);
      nodeSet.add(target);
      links.push({source, target, value: iter._value});
    }

    const nodes: SankeyNode[] = Array.from(nodeSet, (name) => ({name}));
    return {nodes, links};
  }
}
