// 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 m from 'mithril';
import type {
  SqlTable,
  SqlModules,
} from '../../../../dev.perfetto.SqlModules/sql_modules';
import {
  type QueryNode,
  NodeType,
  nextNodeId,
  type NodeContext,
} from '../../../query_node';
import {StructuredQueryBuilder} from '../../structured_query_builder';
import {
  type ColumnInfo,
  columnInfoFromSqlColumn,
  newColumnInfo,
} from '../../column_info';
import type protos from '../../../../../protos';
import {closeModal, showModal} from '../../../../../widgets/modal';
import {TableList} from '../../table_list';
import {redrawModal} from '../../../../../widgets/modal';
import {setValidationError} from '../../node_issues';
import {TableDescription} from '../../widgets';
import type {NodeDetailsAttrs} from '../../../node_types';
import {loadNodeDoc} from '../../node_doc_loader';
import {NodeTitle} from '../../node_styling_widgets';

// Serializable node configuration.
export interface TableSourceNodeAttrs {
  sqlTable?: string;
}

interface TableSelectionResult {
  sqlTable: SqlTable;
  sourceCols: ColumnInfo[];
}

export function modalForTableSelection(
  sqlModules: SqlModules,
): Promise<TableSelectionResult[] | undefined> {
  return new Promise((resolve) => {
    let searchQuery = '';
    const selectedTables = new Set<string>();

    const updateModal = () => {
      showModal({
        key: 'table-selection-modal',
        vAlign: 'TOP',
        className: 'pf-table-selection-modal',
        title:
          selectedTables.size > 0
            ? `Choose tables - ${selectedTables.size} selected`
            : 'Choose a table',
        content: () => {
          return m(
            '.pf-exp-node-panel-help',
            m(TableList, {
              sqlModules,
              onTableClick: handleTableClick,
              searchQuery,
              onSearchQueryChange: (query) => {
                searchQuery = query;
                redrawModal();
              },
              autofocus: true,
              selectedTables,
            }),
          );
        },
        buttons:
          selectedTables.size > 0
            ? [
                {
                  text: `Add ${selectedTables.size} table${selectedTables.size > 1 ? 's' : ''}`,
                  primary: true,
                  action: handleConfirm,
                },
              ]
            : [],
      });
    };

    const handleTableClick = (tableName: string, event: MouseEvent) => {
      if (event.ctrlKey || event.metaKey) {
        // Multi-select mode: toggle selection
        if (selectedTables.has(tableName)) {
          selectedTables.delete(tableName);
        } else {
          selectedTables.add(tableName);
        }
        updateModal();
      } else {
        // Single-select mode: immediately select and close
        const sqlTable = sqlModules.getTable(tableName);
        if (!sqlTable) {
          resolve(undefined);
          return;
        }
        const sourceCols = sqlTable.columns.map((c) =>
          columnInfoFromSqlColumn(c, true),
        );
        resolve([{sqlTable, sourceCols}]);
        closeModal();
      }
    };

    const handleConfirm = () => {
      if (selectedTables.size === 0) {
        resolve(undefined);
        closeModal();
        return;
      }

      const results: TableSelectionResult[] = [];
      for (const tableName of selectedTables) {
        const sqlTable = sqlModules.getTable(tableName);
        if (sqlTable) {
          const sourceCols = sqlTable.columns.map((c) =>
            columnInfoFromSqlColumn(c, true),
          );
          results.push({sqlTable, sourceCols});
        }
      }
      resolve(results);
      closeModal();
    };

    updateModal();
  });
}

export class TableSourceNode implements QueryNode {
  readonly nodeId: string;
  readonly attrs: TableSourceNodeAttrs;
  readonly context: NodeContext;
  // Runtime-resolved SqlTable object (not serializable).
  readonly sqlTable?: SqlTable;
  readonly finalCols: ColumnInfo[];
  nextNodes: QueryNode[];

  constructor(attrs: TableSourceNodeAttrs, context: NodeContext) {
    this.nodeId = nextNodeId();
    this.attrs = attrs;
    this.context = context;
    // Resolve sqlTable from sqlModules using the table name string.
    this.sqlTable = attrs.sqlTable
      ? context.sqlModules?.getTable(attrs.sqlTable)
      : undefined;
    this.finalCols = (
      this.sqlTable?.columns.map((c) => columnInfoFromSqlColumn(c, true)) ?? []
    ).map((col) => newColumnInfo(col, true));
    this.nextNodes = [];
  }

  get type() {
    return NodeType.kTable;
  }

  clone(): QueryNode {
    return new TableSourceNode(this.attrs, this.context);
  }

  nodeSpecificModify(): m.Child {
    return undefined;
  }

  validate(): boolean {
    // Clear any previous errors at the start of validation
    if (this.context.issues) {
      this.context.issues.clear();
    }

    if (this.sqlTable === undefined) {
      setValidationError(this.context, 'No table selected');
      return false;
    }

    return true;
  }

  getTitle(): string {
    return `${this.sqlTable?.name}`;
  }

  nodeDetails(): NodeDetailsAttrs {
    return {
      content: NodeTitle('Table ' + (this.sqlTable?.name ?? '')),
    };
  }

  getStructuredQuery(): protos.PerfettoSqlStructuredQuery | undefined {
    if (!this.validate()) return;
    if (!this.sqlTable) return;

    const columnNames = this.finalCols
      .filter((c) => c.checked)
      .map((c) => c.name);

    const sq = StructuredQueryBuilder.fromTable(
      this.sqlTable.name,
      this.sqlTable.includeKey || undefined,
      columnNames,
      this.nodeId,
    );

    StructuredQueryBuilder.applyNodeColumnSelection(sq, this);
    return sq;
  }

  nodeInfo(): m.Children {
    // Show general documentation
    const docContent = loadNodeDoc('table_source');

    // If a table is selected, also show table-specific information
    if (this.sqlTable != null) {
      return m(
        'div',
        docContent,
        m(
          '.pf-table-source-selected',
          m('h2', 'Selected Table'),
          m(TableDescription, {table: this.sqlTable}),
        ),
      );
    }

    return docContent;
  }
}
