// 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 {Button, type ButtonAttrs, ButtonVariant} from '../../../widgets/button';
import {TextInput} from '../../../widgets/text_input';
import {Icon} from '../../../widgets/icon';
import {TextParagraph} from '../../../widgets/text_paragraph';
import type {SqlTable} from '../../dev.perfetto.SqlModules/sql_modules';
import {perfettoSqlTypeToString} from '../../../trace_processor/perfetto_sql_type';
import {Callout} from '../../../widgets/callout';
import {Intent} from '../../../widgets/common';
import {EmptyState} from '../../../widgets/empty_state';
import type {QueryNode} from '../query_node';
import type {NodeModifySection} from '../node_types';
import {
  PopupMultiSelect,
  type MultiSelectOption,
  type MultiSelectDiff,
} from '../../../widgets/multiselect';
import {classNames} from '../../../base/classnames';
import {Tooltip} from '../../../widgets/tooltip';
import {showModal} from '../../../widgets/modal';
import {Editor} from '../../../widgets/editor';
import {ResizeHandle} from '../../../widgets/resize_handle';
import {findRef, toHTMLElement} from '../../../base/dom_utils';
import {assertExists} from '../../../base/assert';

/**
 * Round action button with consistent styling for Data Explorer.
 * Used for undo/redo/add node buttons - provides unified styling with
 * primary color, filled variant, and round shape.
 */
export interface RoundActionButtonAttrs {
  readonly icon: string;
  readonly title: string;
  readonly onclick?: () => void;
  readonly disabled?: boolean;
  readonly className?: string;
}

export function RoundActionButton(attrs: RoundActionButtonAttrs) {
  return m(Button, {
    icon: attrs.icon,
    title: attrs.title,
    onclick: attrs.onclick,
    disabled: attrs.disabled,
    variant: ButtonVariant.Filled,
    rounded: true,
    iconFilled: true,
    intent: Intent.Primary,
    className: classNames('pf-qb-round-action-button', attrs.className),
  });
}

// Empty state widget for the data explorer with warning variant support
export type ResultsPanelEmptyStateVariant = 'default' | 'warning';

export interface ResultsPanelEmptyStateAttrs {
  readonly icon?: string;
  readonly title?: string;
  readonly variant?: ResultsPanelEmptyStateVariant;
}

export class ResultsPanelEmptyState
  implements m.ClassComponent<ResultsPanelEmptyStateAttrs>
{
  view({attrs, children}: m.CVnode<ResultsPanelEmptyStateAttrs>) {
    const {icon, title, variant = 'default'} = attrs;

    return m(
      '.pf-results-panel-empty-state',
      {
        className: classNames(variant === 'warning' && 'pf-warning'),
      },
      icon &&
        m(Icon, {
          className: 'pf-data-explorer-empty-state__icon',
          icon,
        }),
      title && m('.pf-results-panel-empty-state__message', title),
      children,
    );
  }
}

// Re-export multiselect types for convenience
export type {MultiSelectOption, MultiSelectDiff};

// Action button definition for ListItem
export interface ListItemAction {
  label?: string;
  icon?: string;
  title?: string;
  onclick: () => void;
}

// Widget for displaying an item with icon, name, description, and action button(s)
// Used in lists of added columns, filters, etc.
export interface ListItemAttrs {
  icon: string;
  name: string;
  description: string;
  actions?: ListItemAction[];
  onRemove?: () => void;
  className?: string;
  onclick?: (event: MouseEvent) => void;
}

export class ListItem implements m.ClassComponent<ListItemAttrs> {
  view({attrs}: m.Vnode<ListItemAttrs>) {
    const {icon, name, description, actions, onRemove, onclick} = attrs;

    return m(
      '.pf-exp-list-item',
      {
        className: attrs.className,
        tabindex: 0,
        role: 'listitem',
        onclick,
        onkeydown: (e: KeyboardEvent) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            // Trigger first action on Enter/Space
            actions?.[0]?.onclick();
          } else if (e.key === 'Delete' || e.key === 'Backspace') {
            if (onRemove) {
              e.preventDefault();
              onRemove();
            }
          }
        },
      },
      m(Icon, {icon}),
      m(
        '.pf-exp-list-item-info',
        m('.pf-exp-list-item-name', name),
        m('.pf-exp-list-item-description', description),
      ),
      m('.pf-exp-list-item-actions', this.renderButtons(attrs)),
    );
  }

  private renderButtons(attrs: ListItemAttrs): m.Children {
    const buttons: m.Children = [];

    // Render action buttons
    if (attrs.actions) {
      for (const action of attrs.actions) {
        // Build button attributes based on what's available
        const buttonAttrs: ButtonAttrs = action.label
          ? {
              label: action.label,
              icon: action.icon,
              title: action.title,
              variant: ButtonVariant.Outlined,
              compact: true,
              onclick: action.onclick,
            }
          : {
              icon: action.icon ?? 'help',
              title: action.title,
              variant: ButtonVariant.Outlined,
              compact: true,
              onclick: action.onclick,
            };
        buttons.push(m(Button, buttonAttrs));
      }
    }

    // Remove button
    if (attrs.onRemove) {
      buttons.push(
        m(Button, {
          icon: 'close',
          compact: true,
          onclick: attrs.onRemove,
          title: 'Remove item',
        }),
      );
    }

    return buttons;
  }
}

// Widget for inline label with any control (input, select, multiselect, etc.)
// The children are placed inside the label for proper accessibility
export interface LabeledControlAttrs {
  label: string;
}

export class LabeledControl implements m.ClassComponent<LabeledControlAttrs> {
  view({attrs, children}: m.CVnode<LabeledControlAttrs>) {
    return m('label.pf-exp-labeled-control', m('span', attrs.label), children);
  }
}

// Widget for a draggable list item with drag handle
export interface DraggableItemAttrs {
  index: number;
  onReorder: (fromIndex: number, toIndex: number) => void;
}

export class DraggableItem implements m.ClassComponent<DraggableItemAttrs> {
  view({attrs, children}: m.CVnode<DraggableItemAttrs>) {
    const {index, onReorder} = attrs;

    return m(
      '.pf-exp-draggable-item',
      {
        draggable: true,
        ondragstart: (e: DragEvent) => {
          e.dataTransfer?.setData('text/plain', index.toString());
        },
        ondragover: (e: DragEvent) => {
          e.preventDefault();
        },
        ondrop: (e: DragEvent) => {
          e.preventDefault();
          const from = parseInt(
            e.dataTransfer?.getData('text/plain') ?? '0',
            10,
          );
          onReorder(from, index);
        },
      },
      m('span.pf-exp-drag-handle', '☰'),
      children,
    );
  }
}

// Widget for displaying a list of issues (errors or warnings) in a callout
// Used for validation errors, duplicate column warnings, etc.
export interface IssueListAttrs {
  icon: 'error' | 'warning' | 'info';
  title: string;
  items: string[];
}

export class IssueList implements m.ClassComponent<IssueListAttrs> {
  view({attrs}: m.Vnode<IssueListAttrs>) {
    const {icon, title, items} = attrs;

    if (items.length === 0) {
      return null;
    }

    // Map icon to Intent for proper styling
    const intentMap: Record<string, Intent> = {
      error: Intent.Danger,
      warning: Intent.Warning,
      info: Intent.Primary,
    };
    const intent = intentMap[icon] ?? Intent.None;

    return m(
      Callout,
      {icon, intent},
      m('div', title),
      m(
        'ul.pf-exp-issue-list',
        items.map((item) => m('li', item)),
      ),
    );
  }
}

// Widget for displaying a SQL table's description and columns
// Used in table source node info and join modal
export interface TableDescriptionAttrs {
  table: SqlTable;
}

export class TableDescription
  implements m.ClassComponent<TableDescriptionAttrs>
{
  view({attrs}: m.Vnode<TableDescriptionAttrs>) {
    const {table} = attrs;

    return m(
      '.pf-exp-table-description',
      m(TextParagraph, {text: table.description}),
      m(
        'table.pf-table.pf-table-striped',
        m(
          'thead',
          m('tr', m('th', 'Column'), m('th', 'Type'), m('th', 'Description')),
        ),
        m(
          'tbody',
          table.columns.map((col) => {
            return m(
              'tr',
              m('td', col.name),
              m('td', perfettoSqlTypeToString(col.type)),
              m('td', col.description),
            );
          }),
        ),
      ),
    );
  }
}
// Button for switching between basic and advanced modes
// Styled as a subtle, text-like button to indicate it's a secondary action
export interface AdvancedModeChangeButtonAttrs {
  label: string;
  icon?: string;
  onclick: () => void;
}

export class AdvancedModeChangeButton
  implements m.ClassComponent<AdvancedModeChangeButtonAttrs>
{
  view({attrs}: m.Vnode<AdvancedModeChangeButtonAttrs>) {
    const {label, icon, onclick} = attrs;

    return m(Button, {
      className: 'pf-exp-advanced-mode-button',
      label,
      icon,
      onclick,
      variant: ButtonVariant.Minimal,
    });
  }
}

// Widget for displaying informational text in a styled box
// Similar to the pattern used in timerange node for dynamic mode info
export class InfoBox implements m.ClassComponent {
  view({children}: m.CVnode) {
    return m('.pf-exp-info-box', children);
  }
}

/**
 * Automatically creates error/warning sections from node.context.issues
 * Returns sections to prepend to the node's modify view
 */
export function createErrorSections(node: QueryNode): NodeModifySection[] {
  const sections: NodeModifySection[] = [];

  if (node.context.issues?.queryError) {
    sections.push({
      content: m(
        Callout,
        {icon: 'error'},
        node.context.issues.queryError.message,
      ),
    });
  }

  return sections;
}

/**
 * Reusable list component with empty state and item rendering
 * Used by nodes that display lists of items (filters, aggregations, columns, etc.)
 */
export interface ModifiableItemListAttrs<T> {
  items: T[];
  renderItem: (item: T, index: number) => m.Child;
  emptyStateTitle: string;
  emptyStateIcon?: string;
}

export function ModifiableItemList<T>(
  attrs: ModifiableItemListAttrs<T>,
): m.Child {
  if (attrs.items.length === 0) {
    return m(EmptyState, {
      title: attrs.emptyStateTitle,
      icon: attrs.emptyStateIcon,
    });
  }

  return m(
    '.pf-modifiable-item-list',
    attrs.items.map((item, index) => attrs.renderItem(item, index)),
  );
}

// Widget for inline filter editing form
// Combines editing and creation into a single list item
export interface FormListItemAttrs<T> {
  item: T;
  isValid: boolean;
  onUpdate: (updated: T) => void;
  onRemove?: () => void; // Optional - if not provided, no remove button
  children: m.Children;
}

export class FormListItem<T> implements m.ClassComponent<FormListItemAttrs<T>> {
  view({attrs}: m.Vnode<FormListItemAttrs<T>>) {
    const {isValid, onRemove, children} = attrs;

    return m(
      '.pf-exp-form-list-item',
      {
        className: isValid ? 'pf-valid' : 'pf-invalid',
      },
      m('.pf-exp-form-list-item-content', children),
      // Only show remove button if onRemove is provided
      onRemove &&
        m(
          '.pf-exp-form-list-item-actions',
          m(Button, {
            icon: 'close',
            compact: true,
            onclick: onRemove,
            title: 'Remove item',
          }),
        ),
    );
  }
}

// Material Design outlined input field with label on border
// Pass children as the third argument to m() for select options
export interface OutlinedFieldAttrs {
  label: string;
  value: string;
  onchange?: (e: Event) => void;
  oninput?: (e: Event) => void;
  disabled?: boolean;
  placeholder?: string; // For text inputs
}

export class OutlinedField implements m.ClassComponent<OutlinedFieldAttrs> {
  view({attrs, children}: m.Vnode<OutlinedFieldAttrs>) {
    const {label, value, onchange, oninput, disabled, placeholder} = attrs;

    // Determine if this is a select or input
    // Children can be an array, so check if it has content
    const isSelect =
      children !== undefined &&
      children !== null &&
      (Array.isArray(children) ? children.length > 0 : true);

    return m(
      'fieldset.pf-outlined-field',
      {
        disabled,
      },
      [
        m('legend.pf-outlined-field-legend', label),
        isSelect
          ? m(
              'select.pf-outlined-field-input',
              {
                value,
                onchange,
                disabled,
              },
              children,
            )
          : m('input.pf-outlined-field-input', {
              type: 'text',
              value,
              oninput,
              disabled,
              placeholder,
            }),
      ],
    );
  }
}

// Read-only version of OutlinedField for displaying static information
// Uses the same visual style but shows text instead of an input
export interface OutlinedFieldReadOnlyAttrs {
  label: string;
  value: string;
  className?: string;
}

export class OutlinedFieldReadOnly
  implements m.ClassComponent<OutlinedFieldReadOnlyAttrs>
{
  view({attrs}: m.Vnode<OutlinedFieldReadOnlyAttrs>) {
    const {label, value, className} = attrs;

    return m(
      'fieldset.pf-outlined-field',
      {className},
      m('legend.pf-outlined-field-legend', label),
      m('.pf-outlined-field-input.pf-read-only', value),
    );
  }
}

// Wrapper around PopupMultiSelect with more obvious clickable styling
export interface OutlinedMultiSelectAttrs {
  label: string;
  options: MultiSelectOption[];
  onChange: (diffs: MultiSelectDiff[]) => void;
  showNumSelected?: boolean;
  repeatCheckedItemsAtTop?: boolean;
  compact?: boolean;
}

export class OutlinedMultiSelect
  implements m.ClassComponent<OutlinedMultiSelectAttrs>
{
  view({attrs}: m.Vnode<OutlinedMultiSelectAttrs>) {
    return m('.pf-outlined-multiselect', m(PopupMultiSelect, attrs));
  }
}

// Button styled like an OutlinedField placeholder for adding new items
// Matches the visual style of OutlinedField (border, height, etc.) but acts as a clickable button
export interface AddItemPlaceholderAttrs {
  label: string;
  icon?: string;
  onclick?: () => void;
}

export class AddItemPlaceholder
  implements m.ClassComponent<AddItemPlaceholderAttrs>
{
  view({attrs}: m.Vnode<AddItemPlaceholderAttrs>) {
    const {label, icon, onclick} = attrs;

    return m(
      'button.pf-add-item-placeholder',
      {
        type: 'button',
        onclick,
      },
      [
        icon && m(Icon, {icon}),
        m('span.pf-add-item-placeholder__label', label),
      ],
    );
  }
}

// Generic inline editing list widget
// Renders a list of items with inline editing forms, validation, and an "add" button
export interface InlineEditListAttrs<T> {
  items: T[]; // Can include partial/invalid items during editing
  validate: (item: T) => boolean; // Returns true if item is valid
  renderControls: (
    item: T,
    index: number,
    onUpdate: (updated: T) => void,
  ) => m.Children; // Renders the form controls for editing an item
  onUpdate: (items: T[]) => void; // Called when items array changes
  onValidChange?: () => void; // Called when an item becomes valid (for triggering query rebuilds)
  addButtonLabel: string;
  addButtonIcon?: string;
  emptyItem: () => T; // Factory function to create a new empty item
}

export class InlineEditList<T>
  implements m.ClassComponent<InlineEditListAttrs<T>>
{
  view({attrs}: m.Vnode<InlineEditListAttrs<T>>) {
    const {
      items,
      validate,
      renderControls,
      onUpdate,
      onValidChange,
      addButtonLabel,
      addButtonIcon,
      emptyItem,
    } = attrs;

    const itemViews: m.Child[] = [];

    // Render each item as an inline form
    for (const [index, item] of items.entries()) {
      const isValid = validate(item);

      itemViews.push(
        m(FormListItem<T>, {
          item,
          isValid,
          onUpdate: (updated) => {
            const wasValid = validate(item);
            const nowValid = validate(updated);

            // Update the item in the array
            items[index] = updated;
            onUpdate([...items]);

            // If the item just became valid, trigger the valid change callback
            if (!wasValid && nowValid && onValidChange) {
              onValidChange();
            }
          },
          onRemove: () => {
            items.splice(index, 1);
            onUpdate([...items]);
            onValidChange?.();
          },
          children: renderControls(item, index, (updated) => {
            const wasValid = validate(item);
            const nowValid = validate(updated);

            // Update the item
            items[index] = updated;
            onUpdate([...items]);

            // If the item just became valid, trigger the valid change callback
            if (!wasValid && nowValid && onValidChange) {
              onValidChange();
            }
          }),
        }),
      );
    }

    // Add "Add item" button
    itemViews.push(
      m(AddItemPlaceholder, {
        label: addButtonLabel,
        icon: addButtonIcon,
        onclick: () => {
          items.push(emptyItem());
          onUpdate([...items]);
        },
      }),
    );

    return m('.pf-inline-edit-list', itemViews);
  }
}

// Widget for inline field with label on left and editable/read-only value on right
// Simpler alternative to InlineEditList for single-value fields
export interface InlineFieldAttrs {
  label: string;
  value: string;
  icon?: string;
  editable?: boolean; // If false, field is read-only (default: true)
  placeholder?: string;
  type?: string; // Input type (e.g., 'text', 'number')
  onchange?: (value: string) => void;
  validate?: (value: string) => boolean; // Returns true if valid
  errorMessage?: string; // Tooltip message shown when validation fails
}

export class InlineField implements m.ClassComponent<InlineFieldAttrs> {
  private inputValue: string = '';
  private lastPropValue: string = '';

  view({attrs}: m.Vnode<InlineFieldAttrs>) {
    const {
      label,
      value,
      icon,
      editable = true,
      placeholder,
      type,
      onchange,
      validate,
      errorMessage,
    } = attrs;

    // Only sync input value when the prop value actually changes (external update)
    if (this.lastPropValue !== value) {
      this.inputValue = value;
      this.lastPropValue = value;
    }

    const isValid = validate ? validate(this.inputValue) : true;

    return m(
      '.pf-inline-field',
      {
        className: classNames(!isValid && 'pf-invalid'),
      },
      icon && m(Icon, {icon}),
      m('span.pf-inline-field__label', label),
      editable
        ? m(TextInput, {
            value: this.inputValue,
            placeholder,
            type,
            oninput: (e: Event) => {
              const newValue = (e.target as HTMLInputElement).value;
              this.inputValue = newValue;
              // Always call onchange - validation is just for visual feedback
              onchange?.(newValue);
            },
          })
        : m('span.pf-inline-field__value', value),
      !isValid &&
        m(
          Tooltip,
          {
            trigger: m(Icon, {icon: 'warning'}),
          },
          errorMessage ?? 'Invalid value',
        ),
    );
  }
}

/**
 * Configuration for a warning modal.
 */
export interface WarningModalConfig {
  readonly key: string;
  readonly title: string;
  readonly paragraphs: m.Children[];
  readonly confirmText: string;
}

/**
 * Shows a warning modal with customizable content.
 * Returns a promise that resolves to true if user confirms, false if cancelled.
 */
export function showWarningModal(config: WarningModalConfig): Promise<boolean> {
  return new Promise((resolve) => {
    showModal({
      key: config.key,
      title: config.title,
      icon: 'warning',
      content: m(
        '.pf-warning-modal',
        config.paragraphs.map((p) => m('p', p)),
      ),
      buttons: [
        {
          text: 'Cancel',
          action: () => {
            resolve(false);
          },
        },
        {
          text: config.confirmText,
          primary: true,
          action: () => {
            resolve(true);
          },
        },
      ],
      onClose: () => {
        resolve(false);
      },
    });
  });
}

/**
 * Shows a confirmation modal warning the user that their current state will be lost.
 * This modal is used when importing JSON or loading an example graph.
 *
 * @returns Promise that resolves to true if user confirms, false if cancelled
 */
export function showStateOverwriteWarning(): Promise<boolean> {
  return showWarningModal({
    key: 'state-overwrite-warning',
    title: 'Warning: Current State Will Be Lost',
    paragraphs: [
      'This action will replace your current graph with a new one. All current nodes and connections will be lost.',
      'Do you want to continue?',
    ],
    confirmText: 'Continue',
  });
}

/**
 * Shows a warning modal before exporting to JSON.
 * Warns users that the Data Explorer is in active development and
 * future imports of the exported JSON are not guaranteed.
 *
 * @returns Promise that resolves to true if user confirms, false if cancelled
 */
export function showExportWarning(): Promise<boolean> {
  return showWarningModal({
    key: 'export-warning',
    title: 'Experimental Feature Warning',
    paragraphs: [
      'The Data Explorer is still in active development. The JSON export format may change in future versions.',
      m(
        'strong',
        'We do not guarantee that you will be able to import this JSON file in future versions of the Data Explorer.',
      ),
      'Do you want to continue with the export?',
    ],
    confirmText: 'Export Anyway',
  });
}

// Resizable SQL editor with resize handle
// Provides consistent SQL editing experience across SQL source and join nodes
export interface ResizableSqlEditorAttrs {
  sql: string;
  onUpdate: (text: string) => void;
  onExecute?: (text: string) => void;
  autofocus?: boolean;
}

export class ResizableSqlEditor
  implements m.ClassComponent<ResizableSqlEditorAttrs>
{
  private editorHeight: number = 0;
  private editorElement?: HTMLElement;

  oncreate({dom}: m.VnodeDOM<ResizableSqlEditorAttrs>) {
    this.editorElement = toHTMLElement(assertExists(findRef(dom, 'editor')));
    this.editorElement.style.height = '400px';
  }

  view({attrs}: m.CVnode<ResizableSqlEditorAttrs>) {
    return [
      m(Editor, {
        ref: 'editor',
        text: attrs.sql,
        onUpdate: attrs.onUpdate,
        onExecute: attrs.onExecute,
        autofocus: attrs.autofocus,
      }),
      m(ResizeHandle, {
        onResize: (deltaPx: number) => {
          this.editorHeight += deltaPx;
          this.editorElement!.style.height = `${this.editorHeight}px`;
        },
        onResizeStart: () => {
          this.editorHeight = this.editorElement!.clientHeight;
        },
      }),
    ];
  }
}
