// 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 type protos from '../../protos';
import type m from 'mithril';
import type {SqlModules} from '../dev.perfetto.SqlModules/sql_modules';
import type {ColumnInfo} from './query_builder/column_info';
import type {NodeIssues} from './query_builder/node_issues';
import type {Trace} from '../../public/trace';
import type {NodeDetailsAttrs} from './node_types';

let nodeCounter = 0;
export function nextNodeId(): string {
  return (nodeCounter++).toString();
}

/**
 * Ensures the node counter is higher than any existing ID.
 * Call this after deserializing nodes to prevent ID collisions.
 */
export function ensureCounterAbove(ids: string[]): void {
  for (const id of ids) {
    const numId = parseInt(id, 10);
    if (!isNaN(numId) && numId >= nodeCounter) {
      nodeCounter = numId + 1;
    }
  }
}

export enum NodeType {
  // Sources
  kTable = 'table',
  kSimpleSlices = 'simple_slices',
  kSqlSource = 'sql_source',
  kTimeRangeSource = 'time_range_source',

  // Single node operations
  kAggregation = 'aggregation',
  kModifyColumns = 'modify_columns',
  kAddColumns = 'add_columns',
  kFilterDuring = 'filter_during',
  kFilterIn = 'filter_in',
  kLimitAndOffset = 'limit_and_offset',
  kSort = 'sort',
  kFilter = 'filter',
  kCounterToIntervals = 'counter_to_intervals',

  // Multi node operations
  kIntervalIntersect = 'interval_intersect',
  kUnion = 'union',
  kJoin = 'join',
  kCreateSlices = 'create_slices',

  // Visualization
  kVisualisation = 'visualisation',

  // Dashboard
  kDashboard = 'dashboard',

  // Group (encapsulates a subgraph as a single node)
  kGroup = 'group',

  // Deprecated (kept for backward compatibility)
  kMerge = kJoin,
  kMetrics = 'metrics',
  kTraceSummary = 'trace_summary',
}

export function singleNodeOperation(type: NodeType): boolean {
  switch (type) {
    case NodeType.kAggregation:
    case NodeType.kModifyColumns:
    case NodeType.kAddColumns:
    case NodeType.kFilterDuring:
    case NodeType.kFilterIn:
    case NodeType.kLimitAndOffset:
    case NodeType.kSort:
    case NodeType.kFilter:
    case NodeType.kCounterToIntervals:
    case NodeType.kMetrics:
    case NodeType.kVisualisation:
    case NodeType.kDashboard:
      return true;
    default:
      return false;
  }
}

// Actions that can be performed by nodes on the parent graph.
// These are optional callbacks provided by the parent component.
export interface NodeActions {
  // Create and connect a table node to a target node's input port
  onAddAndConnectTable?: (tableName: string, portIndex: number) => void;
  // Insert a ModifyColumns node on an input at a specific port
  onInsertModifyColumnsNode?: (portIndex: number) => void;
  // Insert a CounterToIntervals node on an input at a specific port
  onInsertCounterToIntervalsNode?: (portIndex: number) => void;
}

// Runtime dependencies shared across nodes. Never serialized.
// Injected by the parent component (DataExplorer) after node creation.
export interface NodeContext {
  trace?: Trace;
  sqlModules?: SqlModules;
  onchange?: () => void;
  actions?: NodeActions;
  issues?: NodeIssues;
  getTableNameForNode?: (nodeId: string) => Promise<string | undefined>;
  requestNodeExecution?: (nodeId: string) => Promise<void>;
  hasOperationChanged?: boolean;
  autoExecute?: boolean;
}

// Specification for secondary inputs with clear cardinality requirements
export interface SecondaryInputSpec {
  // The actual connections (no undefined holes - indexed by port number)
  readonly connections: Map<number, QueryNode>;

  // Cardinality requirements for validation
  readonly min: number; // Minimum required (e.g., 2 for IntervalIntersect)
  readonly max: number | 'unbounded'; // Maximum allowed (e.g., 2 for Join, unbounded for IntervalIntersect)

  // Port names for UI display
  // Can be an array of names or a function that generates a name for a given port index
  readonly portNames: string[] | ((portIndex: number) => string);
}

export interface QueryNode {
  readonly nodeId: string;
  readonly type: NodeType;
  nextNodes: QueryNode[];

  // Columns that are available after applying all operations.
  readonly finalCols: ColumnInfo[];

  // Serializable node configuration. Always JSON-serializable.
  readonly attrs: object;

  // Runtime context (trace, callbacks, issues). Never serialized.
  readonly context: NodeContext;

  // Primary input from above (data flows vertically down)
  primaryInput?: QueryNode;

  // Secondary inputs from the side (horizontal connections)
  secondaryInputs?: SecondaryInputSpec;

  // Inner nodes encapsulated by this node (e.g. GroupNode).
  innerNodes?: QueryNode[];

  validate(): boolean;
  getTitle(): string;
  // Returns either NodeModifyAttrs (new structured pattern) or m.Child (legacy pattern)
  nodeSpecificModify(): unknown;
  nodeDetails(): NodeDetailsAttrs;
  nodeInfo(): m.Children;
  clone(): QueryNode;
  getStructuredQuery(): protos.PerfettoSqlStructuredQuery | undefined;
  onPrevNodesUpdated?(): void;

  // Optional custom results panel for the bottom drawer panel.
  // If provided, Builder renders this instead of the standard ResultsPanel.
  customResultsPanel?(): m.Children;
}

export interface Query {
  sql: string;
  textproto: string;
  standaloneSql: string;
}
