// 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 {exists} from '../../base/utils';
import {Anchor} from '../../widgets/anchor';
import {MenuItem, PopupMenu} from '../../widgets/menu';
import {TreeNode} from '../../widgets/tree';
import type {Args, ArgsDict, ArgValue} from '../sql_utils/args';
import type {Trace} from '../../public/trace';

// Renders slice arguments (key/value pairs) as a subtree.
export function renderArguments(
  trace: Trace,
  args: ArgsDict,
  extraMenuItems?: (key: string, arg: ArgValue) => m.Children,
): m.Children {
  if (hasArgs(args)) {
    return Object.entries(args).map(([key, value]) =>
      renderArgsTree(trace, key, key, value, extraMenuItems),
    );
  }
  return undefined;
}

export function hasArgs(args?: ArgsDict): args is ArgsDict {
  return exists(args) && Object.keys(args).length > 0;
}

function renderArgsTree(
  trace: Trace,
  key: string,
  fullKey: string,
  args: Args,
  extraMenuItems?: (path: string, arg: ArgValue) => m.Children,
): m.Children {
  if (args instanceof Array) {
    return m(
      TreeNode,
      {
        left: key,
        summary: renderArraySummary(args),
      },
      args.map((value, index) =>
        renderArgsTree(
          trace,
          `[${index}]`,
          `${fullKey}[${index}]`,
          value,
          extraMenuItems,
        ),
      ),
    );
  }
  if (args !== null && typeof args === 'object') {
    if (Object.keys(args).length === 1) {
      const [[childName, value]] = Object.entries(args);
      return renderArgsTree(
        trace,
        `${key}.${childName}`,
        `${fullKey}.${childName}`,
        value,
        extraMenuItems,
      );
    }
    return m(
      TreeNode,
      {
        left: key,
        summary: renderDictSummary(args),
      },
      Object.entries(args).map(([childName, child]) =>
        renderArgsTree(
          trace,
          childName,
          `${fullKey}.${childName}`,
          child,
          extraMenuItems,
        ),
      ),
    );
  }
  return m(TreeNode, {
    left: renderArgKey(key, fullKey, args, extraMenuItems),
    right: renderArgValue(args),
  });
}

function renderArgKey(
  key: string,
  fullKey: string,
  value: ArgValue,
  extraMenuItems?: (path: string, arg: ArgValue) => m.Children,
): m.Children {
  if (value === undefined) {
    return key;
  } else {
    return m(
      PopupMenu,
      {trigger: m(Anchor, {icon: Icons.ContextMenu}, key)},
      m(MenuItem, {
        label: 'Copy full key',
        icon: 'content_copy',
        onclick: () => navigator.clipboard.writeText(fullKey),
      }),
      extraMenuItems?.(fullKey, value),
    );
  }
}

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

function renderArraySummary(children: Args[]): m.Children {
  return `[ ... (${children.length} items) ]`;
}

function renderDictSummary(children: ArgsDict): m.Children {
  const summary = Object.keys(children).slice(0, 2).join(', ');
  const remaining = Object.keys(children).length - 2;
  if (remaining > 0) {
    return `{${summary}, ... (${remaining} more items)}`;
  } else {
    return `{${summary}}`;
  }
}

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);
}
