/** @jsx h */

import { h, render } from 'preact';
import { cx } from '@algolia/ui-components-shared';
import type {
  MenuConnectorParams,
  MenuRenderState,
  MenuWidgetDescription,
} from '../../connectors/menu/connectMenu';
import connectMenu from '../../connectors/menu/connectMenu';
import type {
  MenuSelectComponentCSSClasses,
  MenuSelectComponentTemplates,
} from '../../components/MenuSelect/MenuSelect';
import MenuSelect from '../../components/MenuSelect/MenuSelect';
import defaultTemplates from './defaultTemplates';
import {
  getContainerNode,
  createDocumentationMessageGenerator,
} from '../../lib/utils';
import { prepareTemplateProps } from '../../lib/templating';
import { component } from '../../lib/suit';
import type { RendererOptions, Template, WidgetFactory } from '../../types';
import type { PreparedTemplateProps } from '../../lib/templating';

const withUsage = createDocumentationMessageGenerator({ name: 'menu-select' });
const suit = component('MenuSelect');

export type MenuSelectCSSClasses = Partial<{
  /**
   * CSS class to add to the root element.
   */
  root: string | string[];
  /**
   * CSS class to add to the root when there are no items to display
   */
  noRefinementRoot: string | string[];
  /**
   * CSS class to add to the select element.
   */
  select: string | string[];
  /**
   * CSS class to add to the option element.
   */
  option: string | string[];
}>;

export type MenuSelectTemplates = Partial<{
  /**
   * Item template, provided with `label`, `count`, `isRefined` and `value` data properties.
   */
  item: Template<{
    label: string;
    value: string;
    count: number;
    isRefined: boolean;
  }>;
  /**
   * Label of the "see all" option in the select.
   */
  defaultOption: Template;
}>;

export type MenuSelectWidgetParams = {
  /**
   * CSS Selector or HTMLElement to insert the widget.
   */
  container: string | HTMLElement;
  /**
   * Customize the output through templating.
   */
  templates?: MenuSelectTemplates;
  /**
   * CSS classes to add to the wrapping elements.
   */
  cssClasses?: MenuSelectCSSClasses;
};

const renderer =
  ({
    containerNode,
    cssClasses,
    renderState,
    templates,
  }: {
    containerNode: HTMLElement;
    cssClasses: MenuSelectComponentCSSClasses;
    renderState: {
      templateProps?: PreparedTemplateProps<MenuSelectComponentTemplates>;
    };
    templates: MenuSelectTemplates;
  }) =>
  (
    {
      refine,
      items,
      instantSearchInstance,
    }: MenuRenderState & RendererOptions<MenuConnectorParams>,
    isFirstRendering: boolean
  ) => {
    if (isFirstRendering) {
      renderState.templateProps = prepareTemplateProps({
        defaultTemplates,
        templatesConfig: instantSearchInstance.templatesConfig,
        templates,
      });
      return;
    }

    render(
      <MenuSelect
        cssClasses={cssClasses}
        items={items}
        refine={refine}
        templateProps={renderState.templateProps!}
      />,
      containerNode
    );
  };

export type MenuSelectWidget = WidgetFactory<
  MenuWidgetDescription & { $$widgetType: 'ais.menuSelect' },
  MenuConnectorParams,
  MenuSelectWidgetParams
>;

const menuSelect: MenuSelectWidget = function menuSelect(widgetParams) {
  const {
    container,
    attribute,
    sortBy = ['name:asc'],
    limit = 10,
    cssClasses: userCssClasses = {},
    templates = {},
    transformItems,
  } = widgetParams || {};

  if (!container) {
    throw new Error(withUsage('The `container` option is required.'));
  }

  const containerNode = getContainerNode(container);
  const cssClasses = {
    root: cx(suit(), userCssClasses.root),
    noRefinementRoot: cx(
      suit({ modifierName: 'noRefinement' }),
      userCssClasses.noRefinementRoot
    ),
    select: cx(suit({ descendantName: 'select' }), userCssClasses.select),
    option: cx(suit({ descendantName: 'option' }), userCssClasses.option),
  };

  const specializedRenderer = renderer({
    containerNode,
    cssClasses,
    renderState: {},
    templates,
  });

  const makeWidget = connectMenu(specializedRenderer, () =>
    render(null, containerNode)
  );

  return {
    ...makeWidget({ attribute, limit, sortBy, transformItems }),
    $$widgetType: 'ais.menuSelect',
  };
};

export default menuSelect;
