// 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 {classNames} from '../base/classnames';
import {Hotkey, Platform} from '../base/hotkeys';
import {isString} from '../base/object_utils';
import {Icons} from '../base/semantic_icons';
import {raf} from '../core/raf_scheduler';
import {Anchor} from '../widgets/anchor';
import {Button} from '../widgets/button';
import {Callout} from '../widgets/callout';
import {Checkbox} from '../widgets/checkbox';
import {Editor} from '../widgets/editor';
import {EmptyState} from '../widgets/empty_state';
import {Form, FormLabel} from '../widgets/form';
import {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
import {Icon} from '../widgets/icon';
import {Menu, MenuDivider, MenuItem, PopupMenu2} from '../widgets/menu';
import {showModal} from '../widgets/modal';
import {
  MultiSelect,
  MultiSelectDiff,
  PopupMultiSelect,
} from '../widgets/multiselect';
import {Popup, PopupPosition} from '../widgets/popup';
import {Portal} from '../widgets/portal';
import {FilterableSelect, Select} from '../widgets/select';
import {Spinner} from '../widgets/spinner';
import {Switch} from '../widgets/switch';
import {TextInput} from '../widgets/text_input';
import {MultiParagraphText, TextParagraph} from '../widgets/text_paragraph';
import {LazyTreeNode, Tree, TreeNode} from '../widgets/tree';
import {VegaView} from '../widgets/vega_view';

import {createPage} from './pages';
import {PopupMenuButton} from './popup_menu';
import {TableShowcase} from './tables/table_showcase';

const DATA_ENGLISH_LETTER_FREQUENCY = {
  table: [
    {category: 'a', amount: 8.167}, {category: 'b', amount: 1.492},
    {category: 'c', amount: 2.782}, {category: 'd', amount: 4.253},
    {category: 'e', amount: 12.70}, {category: 'f', amount: 2.228},
    {category: 'g', amount: 2.015}, {category: 'h', amount: 6.094},
    {category: 'i', amount: 6.966}, {category: 'j', amount: 0.253},
    {category: 'k', amount: 1.772}, {category: 'l', amount: 4.025},
    {category: 'm', amount: 2.406}, {category: 'n', amount: 6.749},
    {category: 'o', amount: 7.507}, {category: 'p', amount: 1.929},
    {category: 'q', amount: 0.095}, {category: 'r', amount: 5.987},
    {category: 's', amount: 6.327}, {category: 't', amount: 9.056},
    {category: 'u', amount: 2.758}, {category: 'v', amount: 0.978},
    {category: 'w', amount: 2.360}, {category: 'x', amount: 0.250},
    {category: 'y', amount: 1.974}, {category: 'z', amount: 0.074},
  ],
};

const DATA_POLISH_LETTER_FREQUENCY = {
  table: [
    {category: 'a', amount: 8.965}, {category: 'b', amount: 1.482},
    {category: 'c', amount: 3.988}, {category: 'd', amount: 3.293},
    {category: 'e', amount: 7.921}, {category: 'f', amount: 0.312},
    {category: 'g', amount: 1.377}, {category: 'h', amount: 1.072},
    {category: 'i', amount: 8.286}, {category: 'j', amount: 2.343},
    {category: 'k', amount: 3.411}, {category: 'l', amount: 2.136},
    {category: 'm', amount: 2.911}, {category: 'n', amount: 5.600},
    {category: 'o', amount: 7.590}, {category: 'p', amount: 3.101},
    {category: 'q', amount: 0.003}, {category: 'r', amount: 4.571},
    {category: 's', amount: 4.263}, {category: 't', amount: 3.966},
    {category: 'u', amount: 2.347}, {category: 'v', amount: 0.034},
    {category: 'w', amount: 4.549}, {category: 'x', amount: 0.019},
    {category: 'y', amount: 3.857}, {category: 'z', amount: 5.620},
  ],
};

const DATA_EMPTY = {};

const SPEC_BAR_CHART = `
{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "A basic bar chart example, with value labels shown upon mouse hover.",
  "width": 400,
  "height": 200,
  "padding": 5,

  "data": [
    {
      "name": "table"
    }
  ],

  "signals": [
    {
      "name": "tooltip",
      "value": {},
      "on": [
        {"events": "rect:mouseover", "update": "datum"},
        {"events": "rect:mouseout",  "update": "{}"}
      ]
    }
  ],

  "scales": [
    {
      "name": "xscale",
      "type": "band",
      "domain": {"data": "table", "field": "category"},
      "range": "width",
      "padding": 0.05,
      "round": true
    },
    {
      "name": "yscale",
      "domain": {"data": "table", "field": "amount"},
      "nice": true,
      "range": "height"
    }
  ],

  "axes": [
    { "orient": "bottom", "scale": "xscale" },
    { "orient": "left", "scale": "yscale" }
  ],

  "marks": [
    {
      "type": "rect",
      "from": {"data":"table"},
      "encode": {
        "enter": {
          "x": {"scale": "xscale", "field": "category"},
          "width": {"scale": "xscale", "band": 1},
          "y": {"scale": "yscale", "field": "amount"},
          "y2": {"scale": "yscale", "value": 0}
        },
        "update": {
          "fill": {"value": "steelblue"}
        },
        "hover": {
          "fill": {"value": "red"}
        }
      }
    },
    {
      "type": "text",
      "encode": {
        "enter": {
          "align": {"value": "center"},
          "baseline": {"value": "bottom"},
          "fill": {"value": "#333"}
        },
        "update": {
          "x": {"scale": "xscale", "signal": "tooltip.category", "band": 0.5},
          "y": {"scale": "yscale", "signal": "tooltip.amount", "offset": -2},
          "text": {"signal": "tooltip.amount"},
          "fillOpacity": [
            {"test": "datum === tooltip", "value": 0},
            {"value": 1}
          ]
        }
      }
    }
  ]
}
`;

const SPEC_BAR_CHART_LITE = `
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "description": "A simple bar chart with embedded data.",
  "data": {
    "name": "table"
  },
  "mark": "bar",
  "encoding": {
    "x": {"field": "category", "type": "nominal", "axis": {"labelAngle": 0}},
    "y": {"field": "amount", "type": "quantitative"}
  }
}
`;

const SPEC_BROKEN = `{
  "description": 123
}
`;

enum SpecExample {
  BarChart = 'Barchart',
  BarChartLite = 'Barchart (Lite)',
  Broken = 'Broken',
}

enum DataExample {
  English = 'English',
  Polish = 'Polish',
  Empty = 'Empty',
}

function getExampleSpec(example: SpecExample): string {
  switch (example) {
    case SpecExample.BarChart:
      return SPEC_BAR_CHART;
    case SpecExample.BarChartLite:
      return SPEC_BAR_CHART_LITE;
    case SpecExample.Broken:
      return SPEC_BROKEN;
    default:
      const exhaustiveCheck: never = example;
      throw new Error(`Unhandled case: ${exhaustiveCheck}`);
  }
}

function getExampleData(example: DataExample) {
  switch (example) {
    case DataExample.English:
      return DATA_ENGLISH_LETTER_FREQUENCY;
    case DataExample.Polish:
      return DATA_POLISH_LETTER_FREQUENCY;
    case DataExample.Empty:
      return DATA_EMPTY;
    default:
      const exhaustiveCheck: never = example;
      throw new Error(`Unhandled case: ${exhaustiveCheck}`);
  }
}


const options: {[key: string]: boolean} = {
  foobar: false,
  foo: false,
  bar: false,
  baz: false,
  qux: false,
  quux: false,
  corge: false,
  grault: false,
  garply: false,
  waldo: false,
  fred: false,
  plugh: false,
  xyzzy: false,
  thud: false,
};

function PortalButton() {
  let portalOpen = false;

  return {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    view: function({attrs}: any) {
      const {
        zIndex = true,
        absolute = true,
        top = true,
      } = attrs;
      return [
        m(Button, {
          label: 'Toggle Portal',
          onclick: () => {
            portalOpen = !portalOpen;
            raf.scheduleFullRedraw();
          },
        }),
        portalOpen &&
            m(Portal,
              {
                style: {
                  position: absolute && 'absolute',
                  top: top && '0',
                  zIndex: zIndex ? '10' : '0',
                  background: 'white',
                },
              },
              m('', `A very simple portal - a div rendered outside of the normal
              flow of the page`)),
      ];
    },
  };
}

function lorem() {
  const text =
      `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
      tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
      veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
      commodo consequat.Duis aute irure dolor in reprehenderit in voluptate
      velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
      cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
      est laborum.`;
  return m('', {style: {width: '200px'}}, text);
}

function ControlledPopup() {
  let popupOpen = false;

  return {
    view: function() {
      return m(
          Popup,
          {
            trigger:
                m(Button, {label: `${popupOpen ? 'Close' : 'Open'} Popup`}),
            isOpen: popupOpen,
            onChange: (shouldOpen: boolean) => popupOpen = shouldOpen,
          },
          m(Button, {
            label: 'Close Popup',
            onclick: () => {
              popupOpen = !popupOpen;
              raf.scheduleFullRedraw();
            },
          }),
      );
    },
  };
}

type Options = {
  [key: string]: EnumOption|boolean|string;
};

class EnumOption {
  constructor(public initial: string, public options: string[]) {}
}


interface WidgetTitleAttrs {
  label: string;
}

class WidgetTitle implements m.ClassComponent<WidgetTitleAttrs> {
  view({attrs}: m.CVnode<WidgetTitleAttrs>) {
    const {label} = attrs;
    const id = label.replaceAll(' ', '').toLowerCase();
    const href = `#!/widgets#${id}`;
    return m(Anchor, {id, href}, m('h2', label));
  }
}

interface WidgetShowcaseAttrs {
  label: string;
  description?: string;
  initialOpts?: Options;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  renderWidget: (options: any) => any;
  wide?: boolean;
}

// A little helper class to render any vnode with a dynamic set of options
class WidgetShowcase implements m.ClassComponent<WidgetShowcaseAttrs> {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  private optValues: any = {};
  private opts?: Options;

  renderOptions(listItems: m.Child[]): m.Child {
    if (listItems.length === 0) {
      return null;
    }
    return m(
        '.widget-controls',
        m('h3', 'Options'),
        m('ul', listItems),
    );
  }

  oninit({attrs: {initialOpts: opts}}: m.Vnode<WidgetShowcaseAttrs, this>) {
    this.opts = opts;
    if (opts) {
      // Make the initial options values
      for (const key in opts) {
        if (Object.prototype.hasOwnProperty.call(opts, key)) {
          const option = opts[key];
          if (option instanceof EnumOption) {
            this.optValues[key] = option.initial;
          } else if (typeof option === 'boolean') {
            this.optValues[key] = option;
          } else if (isString(option)) {
            this.optValues[key] = option;
          }
        }
      }
    }
  }

  view({attrs}: m.CVnode<WidgetShowcaseAttrs>) {
    const {renderWidget, wide, label, description} = attrs;
    const listItems = [];

    if (this.opts) {
      for (const key in this.opts) {
        if (Object.prototype.hasOwnProperty.call(this.opts, key)) {
          listItems.push(m('li', this.renderControlForOption(key)));
        }
      }
    }

    return [
      m(WidgetTitle, {label}),
      description && m('p', description),
      m(
          '.widget-block',
          m(
              'div',
              {
                class: classNames(
                    'widget-container',
                    wide && 'widget-container-wide',
                    ),
              },
              renderWidget(this.optValues),
              ),
          this.renderOptions(listItems),
          ),
    ];
  }

  private renderControlForOption(key: string) {
    if (!this.opts) return null;
    const value = this.opts[key];
    if (value instanceof EnumOption) {
      return this.renderEnumOption(key, value);
    } else if (typeof value === 'boolean') {
      return this.renderBooleanOption(key);
    } else if (isString(value)) {
      return this.renderStringOption(key);
    } else {
      return null;
    }
  }

  private renderBooleanOption(key: string) {
    return m(Checkbox, {
      checked: this.optValues[key],
      label: key,
      onchange: () => {
        this.optValues[key] = !this.optValues[key];
        raf.scheduleFullRedraw();
      },
    });
  }

  private renderStringOption(key: string) {
    return m(TextInput, {
      placeholder: key,
      value: this.optValues[key],
      oninput: (e: Event) => {
        this.optValues[key] = (e.target as HTMLInputElement).value;
        raf.scheduleFullRedraw();
      },
    });
  }

  private renderEnumOption(key: string, opt: EnumOption) {
    const optionElements = opt.options.map((option: string) => {
      return m('option', {value: option}, option);
    });
    return m(
        Select,
        {
          value: this.optValues[key],
          onchange: (e: Event) => {
            const el = e.target as HTMLSelectElement;
            this.optValues[key] = el.value;
            raf.scheduleFullRedraw();
          },
        },
        optionElements);
  }
}

export const WidgetsPage = createPage({
  view() {
    return m(
        '.widgets-page',
        m('h1', 'Widgets'),
        m(WidgetShowcase, {
          label: 'Button',
          renderWidget: ({label, icon, rightIcon, ...rest}) => m(Button, {
            icon: icon ? 'send' : undefined,
            rightIcon: rightIcon ? 'arrow_forward' : undefined,
            label: label ? 'Button' : '',
            ...rest,
          }),
          initialOpts: {
            label: true,
            icon: true,
            rightIcon: false,
            disabled: false,
            minimal: false,
            active: false,
            compact: false,
          },
        }),
        m(WidgetShowcase, {
          label: 'Checkbox',
          renderWidget: (opts) => m(Checkbox, {label: 'Checkbox', ...opts}),
          initialOpts: {
            disabled: false,
          },
        }),
        m(WidgetShowcase, {
          label: 'Switch',
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          renderWidget: ({label, ...rest}: any) =>
              m(Switch, {label: label ? 'Switch' : undefined, ...rest}),
          initialOpts: {
            label: true,
            disabled: false,
          },
        }),
        m(WidgetShowcase, {
          label: 'Text Input',
          renderWidget: ({placeholder, ...rest}) => m(TextInput, {
            placeholder: placeholder ? 'Placeholder...' : '',
            ...rest,
          }),
          initialOpts: {
            placeholder: true,
            disabled: false,
          },
        }),
        m(WidgetShowcase, {
          label: 'Select',
          renderWidget: (opts) =>
              m(Select,
                opts,
                [
                  m('option', {value: 'foo', label: 'Foo'}),
                  m('option', {value: 'bar', label: 'Bar'}),
                  m('option', {value: 'baz', label: 'Baz'}),
                ]),
          initialOpts: {
            disabled: false,
          },
        }),
        m(WidgetShowcase, {
          label: 'Filterable Select',
          renderWidget: () =>
              m(FilterableSelect, {
                values: ['foo', 'bar', 'baz'],
                onSelected: () => {},
              }),
        }),
        m(WidgetShowcase, {
          label: 'Empty State',
          renderWidget: ({header, content}) =>
              m(EmptyState,
                {
                  header: header && 'No search results found...',
                },
                content && m(Button, {label: 'Try again'})),
          initialOpts: {
            header: true,
            content: true,
          },
        }),
        m(WidgetShowcase, {
          label: 'Anchor',
          renderWidget: ({icon}) => m(
              Anchor,
              {
                icon: icon && 'open_in_new',
                href: 'https://perfetto.dev/docs/',
                target: '_blank',
              },
              'Docs',
              ),
          initialOpts: {
            icon: true,
          },
        }),
        m(WidgetShowcase,
          {
            label: 'Table',
            renderWidget: () => m(TableShowcase), initialOpts: {}, wide: true,
        }),
        m(WidgetShowcase, {
          label: 'Portal',
          description: `A portal is a div rendered out of normal flow
          of the hierarchy.`,
          renderWidget: (opts) => m(PortalButton, opts),
          initialOpts: {
            absolute: true,
            zIndex: true,
            top: true,
          },
        }),
        m(WidgetShowcase, {
          label: 'Popup',
          description: `A popup is a nicely styled portal element whose position is
        dynamically updated to appear to float alongside a specific element on
        the page, even as the element is moved and scrolled around.`,
          renderWidget: (opts) => m(
              Popup,
              {
                trigger: m(Button, {label: 'Toggle Popup'}),
                ...opts,
              },
              lorem(),
              ),
          initialOpts: {
            position: new EnumOption(
                PopupPosition.Auto,
                Object.values(PopupPosition),
                ),
            closeOnEscape: true,
            closeOnOutsideClick: true,
          },
        }),
        m(WidgetShowcase, {
          label: 'Controlled Popup',
        description: `The open/close state of a controlled popup is passed in via
        the 'isOpen' attribute. This means we can get open or close the popup
        from wherever we like. E.g. from a button inside the popup.
        Keeping this state external also means we can modify other parts of the
        page depending on whether the popup is open or not, such as the text
        on this button.
        Note, this is the same component as the popup above, but used in
        controlled mode.`,
          renderWidget: (opts) => m(ControlledPopup, opts),
          initialOpts: {},
        }),
        m(WidgetShowcase, {
          label: 'Icon',
          renderWidget: (opts) => m(Icon, {icon: 'star', ...opts}),
          initialOpts: {filled: false},
        }),
        m(WidgetShowcase, {
          label: 'MultiSelect panel',
          renderWidget: ({...rest}) => m(MultiSelect, {
            options: Object.entries(options).map(([key, value]) => {
              return {
                id: key,
                name: key,
                checked: value,
              };
            }),
            onChange: (diffs: MultiSelectDiff[]) => {
              diffs.forEach(({id, checked}) => {
                options[id] = checked;
              });
              raf.scheduleFullRedraw();
            },
            ...rest,
          }),
          initialOpts: {
            repeatCheckedItemsAtTop: false,
            fixedSize: false,
          },
        }),
        m(WidgetShowcase, {
          label: 'Popup with MultiSelect',
          renderWidget: ({icon, ...rest}) => m(PopupMultiSelect, {
            options: Object.entries(options).map(([key, value]) => {
              return {
                id: key,
                name: key,
                checked: value,
              };
            }),
            popupPosition: PopupPosition.Top,
            label: 'Multi Select',
            icon: icon ? Icons.LibraryAddCheck : undefined,
            onChange: (diffs: MultiSelectDiff[]) => {
              diffs.forEach(({id, checked}) => {
                options[id] = checked;
              });
              raf.scheduleFullRedraw();
            },
            ...rest,
          }),
          initialOpts: {
            icon: true,
            showNumSelected: true,
            repeatCheckedItemsAtTop: false,
          },
        }),
        m(WidgetShowcase, {
          label: 'PopupMenu',
          renderWidget: () => {
            return m(PopupMenuButton, {
              icon: 'description',
              items: [
                {itemType: 'regular', text: 'New', callback: () => {}},
                {itemType: 'regular', text: 'Open', callback: () => {}},
                {itemType: 'regular', text: 'Save', callback: () => {}},
                {itemType: 'regular', text: 'Delete', callback: () => {}},
                {
                  itemType: 'group',
                  text: 'Share',
                  itemId: 'foo',
                  children: [
                    {itemType: 'regular', text: 'Friends', callback: () => {}},
                    {itemType: 'regular', text: 'Family', callback: () => {}},
                    {itemType: 'regular', text: 'Everyone', callback: () => {}},
                  ],
                },
              ],
            });
          },
        }),
        m(WidgetShowcase, {
          label: 'Menu',
          renderWidget: () => m(
              Menu,
              m(MenuItem, {label: 'New', icon: 'add'}),
              m(MenuItem, {label: 'Open', icon: 'folder_open'}),
              m(MenuItem, {label: 'Save', icon: 'save', disabled: true}),
              m(MenuDivider),
              m(MenuItem, {label: 'Delete', icon: 'delete'}),
              m(MenuDivider),
              m(
                  MenuItem,
                  {label: 'Share', icon: 'share'},
                  m(MenuItem, {label: 'Everyone', icon: 'public'}),
                  m(MenuItem, {label: 'Friends', icon: 'group'}),
                  m(
                      MenuItem,
                      {label: 'Specific people', icon: 'person_add'},
                      m(MenuItem, {label: 'Alice', icon: 'person'}),
                      m(MenuItem, {label: 'Bob', icon: 'person'}),
                      ),
                  ),
              m(
                  MenuItem,
                  {label: 'More', icon: 'more_horiz'},
                  m(MenuItem, {label: 'Query', icon: 'database'}),
                  m(MenuItem, {label: 'Download', icon: 'download'}),
                  m(MenuItem, {label: 'Clone', icon: 'copy_all'}),
                  ),
              ),

        }),
        m(WidgetShowcase, {
          label: 'PopupMenu2',
          renderWidget: (opts) => m(
              PopupMenu2,
              {
                trigger: m(Button, {
                  label: 'Menu',
                  rightIcon: Icons.ContextMenu,
                }),
                ...opts,
              },
              m(MenuItem, {label: 'New', icon: 'add'}),
              m(MenuItem, {label: 'Open', icon: 'folder_open'}),
              m(MenuItem, {label: 'Save', icon: 'save', disabled: true}),
              m(MenuDivider),
              m(MenuItem, {label: 'Delete', icon: 'delete'}),
              m(MenuDivider),
              m(
                  MenuItem,
                  {label: 'Share', icon: 'share'},
                  m(MenuItem, {label: 'Everyone', icon: 'public'}),
                  m(MenuItem, {label: 'Friends', icon: 'group'}),
                  m(
                      MenuItem,
                      {label: 'Specific people', icon: 'person_add'},
                      m(MenuItem, {label: 'Alice', icon: 'person'}),
                      m(MenuItem, {label: 'Bob', icon: 'person'}),
                      ),
                  ),
              m(
                  MenuItem,
                  {label: 'More', icon: 'more_horiz'},
                  m(MenuItem, {label: 'Query', icon: 'database'}),
                  m(MenuItem, {label: 'Download', icon: 'download'}),
                  m(MenuItem, {label: 'Clone', icon: 'copy_all'}),
                  ),
              ),
          initialOpts: {
            popupPosition: new EnumOption(
                PopupPosition.Bottom,
                Object.values(PopupPosition),
                ),
          },
        }),
        m(WidgetShowcase, {
          label: 'Spinner',
          description: `Simple spinner, rotates forever.
            Width and height match the font size.`,
          renderWidget: ({fontSize, easing}) =>
              m('', {style: {fontSize}}, m(Spinner, {easing})),
          initialOpts: {
            fontSize: new EnumOption(
                '16px',
                ['12px', '16px', '24px', '32px', '64px', '128px'],
                ),
            easing: false,
          },
        }),
        m(WidgetShowcase, {
          label: 'Tree',
          renderWidget: (opts) => m(
            Tree,
            opts,
            m(TreeNode, {left: 'Name', right: 'my_event', icon: 'badge'}),
            m(TreeNode, {left: 'CPU', right: '2', icon: 'memory'}),
            m(TreeNode,
              {left: 'Start time', right: '1s 435ms', icon: 'schedule'}),
            m(TreeNode, {left: 'Duration', right: '86ms', icon: 'timer'}),
            m(TreeNode, {
              left: 'SQL',
              right: m(
                PopupMenu2,
                {
                  popupPosition: PopupPosition.RightStart,
                  trigger: m(Anchor, {
                    icon: Icons.ContextMenu,
                  }, 'SELECT * FROM raw WHERE id = 123'),
                },
                m(MenuItem, {
                  label: 'Copy SQL Query',
                  icon: 'content_copy',
                }),
                m(MenuItem, {
                  label: 'Execute Query in new tab',
                  icon: 'open_in_new',
                }),
                ),
            }),
            m(TreeNode, {
              icon: 'account_tree',
              left: 'Process',
              right: m(Anchor, {icon: 'open_in_new'}, '/bin/foo[789]'),
            }),
            m(TreeNode, {
              left: 'Thread',
              right: m(Anchor, {icon: 'open_in_new'}, 'my_thread[456]'),
            }),
            m(
              TreeNode,
              {
                left: 'Args',
                summary: 'foo: string, baz: string, quux: string[4]',
              },
              m(TreeNode, {left: 'foo', right: 'bar'}),
              m(TreeNode, {left: 'baz', right: 'qux'}),
              m(
                TreeNode,
                {left: 'quux', summary: 'string[4]'},
                m(TreeNode, {left: '[0]', right: 'corge'}),
                m(TreeNode, {left: '[1]', right: 'grault'}),
                m(TreeNode, {left: '[2]', right: 'garply'}),
                m(TreeNode, {left: '[3]', right: 'waldo'}),
                ),
              ),
            m(LazyTreeNode, {
              left: 'Lazy',
              icon: 'bedtime',
              fetchData: async () => {
                await new Promise((r) => setTimeout(r, 1000));
                return () => m(TreeNode, {left: 'foo'});
              },
            }),
            m(LazyTreeNode, {
              left: 'Dynamic',
              unloadOnCollapse: true,
              icon: 'bedtime',
              fetchData: async () => {
                await new Promise((r) => setTimeout(r, 1000));
                return () => m(TreeNode, {left: 'foo'});
              },
            }),
            ),
          wide: true,
        }),
        m(
          WidgetShowcase, {
            label: 'Form',
            renderWidget: () => renderForm('form'),
          }),
        m(WidgetShowcase, {
            label: 'Nested Popups',
            renderWidget: () => m(
              Popup,
              {
                trigger: m(Button, {label: 'Open the popup'}),
              },
              m(PopupMenu2,
                {
                  trigger: m(Button, {label: 'Select an option'}),
                },
                m(MenuItem, {label: 'Option 1'}),
                m(MenuItem, {label: 'Option 2'}),
              ),
              m(Button, {
                label: 'Done',
                dismissPopup: true,
              }),
            ),
          }),
          m(
            WidgetShowcase, {
              label: 'Callout',
              renderWidget: () => m(
                Callout,
                {
                  icon: 'info',
                },
                'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
                'Nulla rhoncus tempor neque, sed malesuada eros dapibus vel. ' +
                'Aliquam in ligula vitae tortor porttitor laoreet iaculis ' +
                'finibus est.',
              ),
            }),
          m(WidgetShowcase, {
            label: 'Editor',
            renderWidget: () => m(Editor),
          }),
          m(WidgetShowcase, {
            label: 'VegaView',
            renderWidget: (opt) => m(VegaView, {
              spec: getExampleSpec(opt.exampleSpec),
              data: getExampleData(opt.exampleData),
            }),
            initialOpts: {
              exampleSpec: new EnumOption(
                SpecExample.BarChart,
                Object.values(SpecExample),
              ),
              exampleData: new EnumOption(
                DataExample.English,
                Object.values(DataExample),
              ),

            },
          }),
          m(
            WidgetShowcase, {
              label: 'Form within PopupMenu2',
              description: `A form placed inside a popup menu works just fine,
              and the cancel/submit buttons also dismiss the popup. A bit more
              margin is added around it too, which improves the look and feel.`,
              renderWidget: () => m(
                PopupMenu2,
                {
                  trigger: m(Button, {label: 'Popup!'}),
                },
                m(MenuItem,
                  {
                    label: 'Open form...',
                  },
                  renderForm('popup-form'),
                ),
              ),
            }),
          m(
            WidgetShowcase, {
              label: 'Hotkey',
              renderWidget: (opts) => {
                if (opts.platform === 'auto') {
                  return m(HotkeyGlyphs, {hotkey: opts.hotkey as Hotkey});
                } else {
                  const platform = opts.platform as Platform;
                  return m(HotkeyGlyphs, {
                    hotkey: opts.hotkey as Hotkey,
                    spoof: platform,
                  });
                }
              },
              initialOpts: {
                hotkey: 'Mod+Shift+P',
                platform: new EnumOption('auto', ['auto', 'Mac', 'PC']),
              },
            }),
          m(
            WidgetShowcase, {
              label: 'Text Paragraph',
              description: `A basic formatted text paragraph with wrapping. If
              it is desirable to preserve the original text format/line breaks,
              set the compressSpace attribute to false.`,
              renderWidget: (opts) => {
                return m(TextParagraph, {
                  text: `Lorem ipsum dolor sit amet, consectetur adipiscing
                         elit. Nulla rhoncus tempor neque, sed malesuada eros
                         dapibus vel. Aliquam in ligula vitae tortor porttitor
                         laoreet iaculis finibus est.`,
                  compressSpace: opts.compressSpace,
                });
              },
              initialOpts: {
                compressSpace: true,
              },
            }),
          m(
            WidgetShowcase, {
              label: 'Multi Paragraph Text',
              description: `A wrapper for multiple paragraph widgets.`,
              renderWidget: () => {
                return m(MultiParagraphText,
                 m(TextParagraph, {
                  text: `Lorem ipsum dolor sit amet, consectetur adipiscing
                         elit. Nulla rhoncus tempor neque, sed malesuada eros
                         dapibus vel. Aliquam in ligula vitae tortor porttitor
                         laoreet iaculis finibus est.`,
                  compressSpace: true,
                }), m(TextParagraph, {
                  text: `Sed ut perspiciatis unde omnis iste natus error sit
                         voluptatem accusantium doloremque laudantium, totam rem
                         aperiam, eaque ipsa quae ab illo inventore veritatis et
                         quasi architecto beatae vitae dicta sunt explicabo.
                         Nemo enim ipsam voluptatem quia voluptas sit aspernatur
                         aut odit aut fugit, sed quia consequuntur magni dolores
                         eos qui ratione voluptatem sequi nesciunt.`,
                  compressSpace: true,
                }),
                );
              },
            }),
            m(
              WidgetShowcase, {
                label: 'Modal',
                description: `A helper for modal dialog.`,
                renderWidget: () => m(ModalShowcase),
              }),
    );
  },
});

class ModalShowcase implements m.ClassComponent {
  private static counter = 0;

  private static log(txt: string) {
    const mwlogs = document.getElementById('mwlogs');
    if (!mwlogs || !(mwlogs instanceof HTMLTextAreaElement)) return;
    const time = new Date().toLocaleTimeString();
    mwlogs.value += `[${time}] ${txt}\n`;
    mwlogs.scrollTop = mwlogs.scrollHeight;
  }

  private static showModalDialog(staticContent = false) {
    const id = `N=${++ModalShowcase.counter}`;
    ModalShowcase.log(`Open ${id}`);
    const logOnClose = () => ModalShowcase.log(`Close ${id}`);

    let content;
    if (staticContent) {
      content = m('.modal-pre', 'Content of the modal dialog.\nEnd of content');
    } else {
      const component = {
        oninit: function(vnode: m.Vnode<{}, {progress: number}>) {
          vnode.state.progress = (vnode.state.progress as number || 0) + 1;
        },
        view: function(vnode: m.Vnode<{}, {progress: number}>) {
          vnode.state.progress = (vnode.state.progress + 1) % 100;
          raf.scheduleFullRedraw();
          return m(
              'div',
              m('div', 'You should see an animating progress bar'),
              m('progress', {value: vnode.state.progress, max: 100}),
          );
        },
      } as m.Component<{}, {progress: number}>;
      content = () => m(component);
    }
    const closePromise = showModal({
      title: `Modal dialog ${id}`,
      buttons: [
        {text: 'OK', action: () => ModalShowcase.log(`OK ${id}`)},
        {text: 'Cancel', action: () => ModalShowcase.log(`Cancel ${id}`)},
        {
          text: 'Show another now',
          action: () => ModalShowcase.showModalDialog(),
        },
        {
          text: 'Show another in 2s',
          action: () => setTimeout(() => ModalShowcase.showModalDialog(), 2000),
        },
      ],
      content,
    });
    closePromise.then(logOnClose);
  }

  view() {
    return m(
        'div',
        {
          style: {
            'display': 'flex',
            'flex-direction': 'column',
            'width': '100%',
          },
        },
        m('textarea', {
          id: 'mwlogs',
          readonly: 'readonly',
          rows: '8',
          placeholder: 'Logs will appear here',
        }),
        m('input[type=button]', {
          value: 'Show modal (static)',
          onclick: () => ModalShowcase.showModalDialog(true),
        }),
        m('input[type=button]', {
          value: 'Show modal (dynamic)',
          onclick: () => ModalShowcase.showModalDialog(false),
        }),
    );
  }
}  // class ModalShowcase

function renderForm(id: string) {
  return m(
      Form,
      {
        submitLabel: 'Submit',
        submitIcon: 'send',
        cancelLabel: 'Cancel',
        resetLabel: 'Reset',
        onSubmit: () => window.alert('Form submitted!'),
      },
      m(FormLabel,
        {for: `${id}-foo`,
        },
        'Foo'),
      m(TextInput, {id: `${id}-foo`}),
      m(FormLabel,
        {for: `${id}-bar`,
        },
        'Bar'),
      m(Select,
        {id: `${id}-bar`},
        [
          m('option', {value: 'foo', label: 'Foo'}),
          m('option', {value: 'bar', label: 'Bar'}),
          m('option', {value: 'baz', label: 'Baz'}),
        ]),
  );
}
