// Copyright (C) 2023 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 {isString} from '../../base/object_utils';
import {Icons} from '../../base/semantic_icons';
import {sqliteString} from '../../base/string_utils';
import {exists} from '../../base/utils';
import {ArgNode, convertArgsToTree, Key} from './slice_args_parser';
import {Anchor} from '../../widgets/anchor';
import {MenuItem, PopupMenu2} from '../../widgets/menu';
import {TreeNode} from '../../widgets/tree';
import {Arg} from '../sql_utils/args';
import {assertExists} from '../../base/logging';
import {getSqlTableDescription} from '../widgets/sql/table/sql_table_registry';
import {Trace} from '../../public/trace';
import {extensions} from '../extensions';

// Renders slice arguments (key/value pairs) as a subtree.
export function renderArguments(trace: Trace, args: Arg[]): m.Children {
  if (args.length > 0) {
    const tree = convertArgsToTree(args);
    return renderArgTreeNodes(trace, tree);
  } else {
    return undefined;
  }
}

export function hasArgs(args?: Arg[]): args is Arg[] {
  return exists(args) && args.length > 0;
}

function renderArgTreeNodes(trace: Trace, args: ArgNode<Arg>[]): m.Children {
  return args.map((arg) => {
    const {key, value, children} = arg;
    if (children && children.length === 1) {
      // If we only have one child, collapse into self and combine keys
      const child = children[0];
      const compositeArg = {
        ...child,
        key: stringifyKey(key, child.key),
      };
      return renderArgTreeNodes(trace, [compositeArg]);
    } else {
      return m(
        TreeNode,
        {
          left: renderArgKey(trace, stringifyKey(key), value),
          right: exists(value) && renderArgValue(value),
          summary: children && renderSummary(children),
        },
        children && renderArgTreeNodes(trace, children),
      );
    }
  });
}

function renderArgKey(trace: Trace, key: string, value?: Arg): m.Children {
  if (value === undefined) {
    return key;
  } else {
    const {key: fullKey, displayValue} = value;
    return m(
      PopupMenu2,
      {trigger: m(Anchor, {icon: Icons.ContextMenu}, key)},
      m(MenuItem, {
        label: 'Copy full key',
        icon: 'content_copy',
        onclick: () => navigator.clipboard.writeText(fullKey),
      }),
      m(MenuItem, {
        label: 'Find slices with same arg value',
        icon: 'search',
        onclick: () => {
          extensions.addSqlTableTab(trace, {
            table: assertExists(getSqlTableDescription('slice')),
            filters: [
              {
                op: (cols) => `${cols[0]} = ${sqliteString(displayValue)}`,
                columns: [
                  {
                    column: 'display_value',
                    source: {
                      table: 'args',
                      joinOn: {
                        arg_set_id: 'arg_set_id',
                        key: sqliteString(fullKey),
                      },
                    },
                  },
                ],
              },
            ],
          });
        },
      }),
      m(MenuItem, {
        label: 'Visualize argument values',
        icon: 'query_stats',
        onclick: () => {
          extensions.addVisualizedArgTracks(trace, fullKey);
        },
      }),
    );
  }
}

function renderArgValue({value}: Arg): m.Children {
  if (isWebLink(value)) {
    return renderWebLink(value);
  } else {
    return `${value}`;
  }
}

function renderSummary(children: ArgNode<Arg>[]): m.Children {
  const summary = children
    .slice(0, 2)
    .map(({key}) => key)
    .join(', ');
  const remaining = children.length - 2;
  if (remaining > 0) {
    return `{${summary}, ... (${remaining} more items)}`;
  } else {
    return `{${summary}}`;
  }
}

function stringifyKey(...key: Key[]): string {
  return key
    .map((element, index) => {
      if (typeof element === 'number') {
        return `[${element}]`;
      } else {
        return (index === 0 ? '' : '.') + element;
      }
    })
    .join('');
}

function isWebLink(value: unknown): value is string {
  return (
    isString(value) &&
    (value.startsWith('http://') || value.startsWith('https://'))
  );
}

function renderWebLink(url: string): m.Children {
  return m(Anchor, {href: url, target: '_blank', icon: 'open_in_new'}, url);
}
