// 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 {findRef, toHTMLElement} from '../../base/dom_utils';
import {download} from '../../base/download_utils';
import {stringifyJsonWithBigints} from '../../base/json_utils';
import {assertExists} from '../../base/logging';
import {Icons} from '../../base/semantic_icons';
import {
  formatAsDelimited,
  formatAsMarkdownTable,
  QueryResponse,
} from '../../components/query_table/queries';
import {DataGridDataSource} from '../../components/widgets/data_grid/common';
import {DataGrid} from '../../components/widgets/data_grid/data_grid';
import {InMemoryDataSource} from '../../components/widgets/data_grid/in_memory_data_source';
import {QueryHistoryComponent} from '../../components/widgets/query_history';
import {Trace} from '../../public/trace';
import {Box} from '../../widgets/box';
import {Button, ButtonVariant} from '../../widgets/button';
import {Callout} from '../../widgets/callout';
import {Intent} from '../../widgets/common';
import {Editor} from '../../widgets/editor';
import {HotkeyGlyphs} from '../../widgets/hotkey_glyphs';
import {MenuItem, PopupMenu} from '../../widgets/menu';
import {ResizeHandle} from '../../widgets/resize_handle';
import {Stack, StackAuto} from '../../widgets/stack';
import {
  CopyHelper,
  CopyToClipboardButton,
} from '../../widgets/copy_to_clipboard_button';
import {Anchor} from '../../widgets/anchor';

const HIDE_PERFETTO_SQL_AGENT_BANNER_KEY = 'hidePerfettoSqlAgentBanner';

export interface QueryPageAttrs {
  readonly trace: Trace;
  readonly editorText: string;
  readonly executedQuery?: string;
  readonly queryResult?: QueryResponse;

  onEditorContentUpdate?(content: string): void;

  onExecute?(query: string): void;
}

export class QueryPage implements m.ClassComponent<QueryPageAttrs> {
  private dataSource?: DataGridDataSource;
  private editorHeight: number = 0;
  private editorElement?: HTMLElement;
  private dataGridCopyHelper = new CopyHelper();

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

  onbeforeupdate(
    vnode: m.Vnode<QueryPageAttrs>,
    oldVnode: m.Vnode<QueryPageAttrs>,
  ) {
    // Update the datasource if present
    if (vnode.attrs.queryResult !== oldVnode.attrs.queryResult) {
      if (vnode.attrs.queryResult) {
        this.dataSource = new InMemoryDataSource(vnode.attrs.queryResult.rows);
      } else {
        this.dataSource = undefined;
      }
    }
  }

  view({attrs}: m.CVnode<QueryPageAttrs>) {
    return m(
      '.pf-query-page',
      m(Box, {className: 'pf-query-page__toolbar'}, [
        m(Stack, {orientation: 'horizontal'}, [
          m(Button, {
            label: 'Run Query',
            icon: 'play_arrow',
            intent: Intent.Primary,
            variant: ButtonVariant.Filled,
            onclick: () => {
              attrs.onExecute?.(attrs.editorText);
            },
          }),
          m(
            Stack,
            {
              orientation: 'horizontal',
              className: 'pf-query-page__hotkeys',
            },
            'or press',
            m(HotkeyGlyphs, {hotkey: 'Mod+Enter'}),
          ),
          m(StackAuto), // The spacer pushes the following buttons to the right.
          attrs.trace.isInternalUser &&
            m(Button, {
              icon: 'wand_stars',
              title:
                'Generate SQL queries with the Perfetto SQL Agent! Give feedback: go/perfetto-llm-bug',
              label: 'Generate SQL Queries with AI',
              onclick: () => {
                window.open('http://go/perfetto-sql-agent', '_blank');
              },
            }),
          m(CopyToClipboardButton, {
            textToCopy: attrs.editorText,
            title: 'Copy query to clipboard',
            label: 'Copy Query',
          }),
        ]),
      ]),
      this.shouldDisplayPerfettoSqlAgentBanner(attrs) &&
        m(
          Box,
          m(
            Callout,
            {
              icon: 'wand_stars',
              dismissible: true,
              onDismiss: () => {
                this.hidePerfettoSqlAgentBanner();
              },
            },
            [
              'Try out the ',
              m(
                Anchor,
                {
                  href: 'http://go/perfetto-sql-agent',
                  target: '_blank',
                  icon: Icons.ExternalLink,
                },
                'Perfetto SQL Agent',
              ),
              ' to generate SQL queries and ',
              m(
                Anchor,
                {
                  href: 'http://go/perfetto-llm-user-guide#report-issues',
                  target: '_blank',
                  icon: Icons.ExternalLink,
                },
                'give feedback',
              ),
              '!',
            ],
          ),
        ),
      attrs.editorText.includes('"') &&
        m(
          Box,
          m(
            Callout,
            {icon: 'warning', intent: Intent.None},
            `" (double quote) character observed in query; if this is being used to ` +
              `define a string, please use ' (single quote) instead. Using double quotes ` +
              `can cause subtle problems which are very hard to debug.`,
          ),
        ),
      m(Editor, {
        ref: 'editor',
        language: 'perfetto-sql',
        text: attrs.editorText,
        onUpdate: attrs.onEditorContentUpdate,
        onExecute: attrs.onExecute,
      }),
      m(ResizeHandle, {
        onResize: (deltaPx: number) => {
          this.editorHeight += deltaPx;
          this.editorElement!.style.height = `${this.editorHeight}px`;
        },
        onResizeStart: () => {
          this.editorHeight = this.editorElement!.clientHeight;
        },
      }),
      this.dataSource &&
        attrs.queryResult &&
        this.renderQueryResult(attrs.queryResult, this.dataSource),
      m(QueryHistoryComponent, {
        className: 'pf-query-page__history',
        trace: attrs.trace,
        runQuery: (query: string) => {
          attrs.onExecute?.(query);
        },
        setQuery: (query: string) => {
          attrs.onEditorContentUpdate?.(query);
        },
      }),
    );
  }

  private renderQueryResult(
    queryResult: QueryResponse,
    dataSource: DataGridDataSource,
  ) {
    const queryTimeString = `${queryResult.durationMs.toFixed(1)} ms`;
    if (queryResult.error) {
      return m(
        '.pf-query-page__query-error',
        `SQL error: ${queryResult.error}`,
      );
    } else {
      return [
        queryResult.statementWithOutputCount > 1 &&
          m(Box, [
            m(Callout, {icon: 'warning', intent: Intent.None}, [
              `${queryResult.statementWithOutputCount} out of ${queryResult.statementCount} `,
              'statements returned a result. ',
              'Only the results for the last statement are displayed.',
            ]),
          ]),
        m(DataGrid, {
          className: 'pf-query-page__results',
          data: dataSource,
          columns: queryResult.columns.map((c) => ({name: c})),
          toolbarItemsLeft: m(
            'span.pf-query-page__results-summary',
            `Returned ${queryResult.totalRowCount.toLocaleString()} rows in ${queryTimeString}`,
          ),
          toolbarItemsRight: [
            this.renderCopyButton(queryResult),
            this.renderDownloadButton(queryResult),
          ],
        }),
      ];
    }
  }

  private renderCopyButton(resp: QueryResponse) {
    const helper = this.dataGridCopyHelper;
    const label = helper.copied ? 'Copied' : 'Copy';
    const icon = helper.copied ? Icons.Check : Icons.Copy;
    const intent = helper.copied ? Intent.Success : Intent.None;

    return m(
      PopupMenu,
      {
        trigger: m(Button, {
          icon,
          intent,
          title: 'Copy results to clipboard',
          label,
        }),
      },
      [
        m(MenuItem, {
          label: 'TSV',
          onclick: async () => {
            const content = formatAsDelimited(resp);
            await helper.copy(content);
          },
        }),
        m(MenuItem, {
          label: 'Markdown',
          onclick: async () => {
            const content = formatAsMarkdownTable(resp);
            await helper.copy(content);
          },
        }),
        m(MenuItem, {
          label: 'JSON',
          onclick: async () => {
            const content = stringifyJsonWithBigints(resp.rows);
            await helper.copy(content);
          },
        }),
      ],
    );
  }

  private renderDownloadButton(resp: QueryResponse) {
    return m(
      PopupMenu,
      {
        trigger: m(Button, {
          icon: Icons.Download,
          title: 'Download data',
          label: 'Download',
        }),
      },
      [
        m(MenuItem, {
          label: 'TSV',
          onclick: () => {
            const content = formatAsDelimited(resp);
            download({
              content,
              mimeType: 'text/tab-separated-values',
              fileName: 'query_result.tsv',
            });
          },
        }),
        m(MenuItem, {
          label: 'Markdown',
          onclick: () => {
            const content = formatAsMarkdownTable(resp);
            download({
              content,
              mimeType: 'text/markdown',
              fileName: 'query_result.md',
            });
          },
        }),
        m(MenuItem, {
          label: 'JSON',
          onclick: () => {
            const content = stringifyJsonWithBigints(resp.rows, 2);
            download({
              content,
              mimeType: 'text/json',
              fileName: 'query_result.json',
            });
          },
        }),
      ],
    );
  }

  private shouldDisplayPerfettoSqlAgentBanner(attrs: QueryPageAttrs) {
    return (
      attrs.trace.isInternalUser &&
      localStorage.getItem(HIDE_PERFETTO_SQL_AGENT_BANNER_KEY) !== 'true'
    );
  }

  private hidePerfettoSqlAgentBanner() {
    localStorage.setItem(HIDE_PERFETTO_SQL_AGENT_BANNER_KEY, 'true');
  }
}
