import type { PropertyPaneConfig } from "constants/PropertyControlConstants";
import type { WidgetProps } from "widgets/BaseWidget";
import type { RenderMode } from "constants/WidgetConstants";
import * as log from "loglevel";
import type {
  AutocompletionDefinitions,
  AutoLayoutConfig,
  CanvasWidgetStructure,
  WidgetConfigProps,
  WidgetMethods,
} from "WidgetProvider/constants";
import {
  addPropertyConfigIds,
  addSearchConfigToPanelConfig,
  convertFunctionsToString,
  enhancePropertyPaneConfig,
  generatePropertyPaneSearchConfig,
  PropertyPaneConfigTypes,
} from "./helpers";
import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants";
import type BaseWidget from "widgets/BaseWidget";
import { flow } from "lodash";
import { generateReactKey } from "utils/generators";
import {
  WidgetFeaturePropertyEnhancements,
  WidgetFeatureProps,
} from "../../utils/WidgetFeatures";
import type { RegisteredWidgetFeatures } from "../../utils/WidgetFeatures";
// import { WIDGETS_COUNT } from "widgets";
import type { SetterConfig } from "entities/AppTheming";
import { freeze, memoize } from "./decorators";

type WidgetDerivedPropertyType = any;
export type DerivedPropertiesMap = Record<string, string>;
export type WidgetType = (typeof WidgetFactory.widgetTypes)[number];

class WidgetFactory {
  static widgetTypes: Record<string, string> = {};

  static widgetConfigMap: Map<
    WidgetType,
    Partial<WidgetProps> & WidgetConfigProps & { type: string }
  > = new Map();

  static widgetsMap: Map<WidgetType, typeof BaseWidget> = new Map();

  static widgetBuilderMap: Map<WidgetType, any> = new Map();

  static initialize(
    widgets: [
      typeof BaseWidget,
      (widgetProps: CanvasWidgetStructure) => React.ReactNode,
    ][],
  ) {
    const start = performance.now();

    for (const [widget, builder] of widgets) {
      WidgetFactory.widgetsMap.set(widget.type, widget);

      WidgetFactory.widgetTypes[widget.type] = widget.type;

      WidgetFactory.widgetBuilderMap.set(widget.type, builder);

      WidgetFactory.configureWidget(widget);

      WidgetFactory.preloadConfig(widget);
    }

    log.debug("Widget registration took: ", performance.now() - start, "ms");
  }

  private static preloadConfig(widget: typeof BaseWidget) {
    if (widget.preloadConfig) {
      // Call functions so we can store the configs in the memo cache
      WidgetFactory.getWidgetPropertyPaneCombinedConfig(widget.type);
      WidgetFactory.getWidgetPropertyPaneConfig(widget.type);
      WidgetFactory.getWidgetPropertyPaneContentConfig(widget.type);
      WidgetFactory.getWidgetPropertyPaneStyleConfig(widget.type);
      WidgetFactory.getWidgetPropertyPaneSearchConfig(widget.type);
      WidgetFactory.getWidgetAutoLayoutConfig(widget.type);
    }
  }

  private static configureWidget(widget: typeof BaseWidget) {
    const config = widget.getConfig();

    const features = widget.getFeatures();

    let enhancedFeatures: Record<string, unknown> = {};

    if (features) {
      Object.keys(features).forEach((registeredFeature: string) => {
        enhancedFeatures = Object.assign(
          {},
          WidgetFeatureProps[registeredFeature as RegisteredWidgetFeatures],
          WidgetFeaturePropertyEnhancements[
            registeredFeature as RegisteredWidgetFeatures
          ](widget),
        );
      });
    }

    const _config = {
      type: widget.type,
      ...widget.getDefaults(),
      ...enhancedFeatures,
      searchTags: config.searchTags,
      tags: config.tags,
      hideCard: !!config.hideCard || !config.iconSVG,
      isDeprecated: !!config.isDeprecated,
      replacement: config.replacement,
      displayName: config.name,
      key: generateReactKey(),
      iconSVG: config.iconSVG,
      isCanvas: config.isCanvas,
      needsHeightForContent: config.needsHeightForContent,
    };

    WidgetFactory.widgetConfigMap.set(widget.type, Object.freeze(_config));
  }

  static get(type: WidgetType) {
    const widget = WidgetFactory.widgetsMap.get(type);

    if (widget) {
      return widget;
    } else {
      log.error(`Widget is not defined with type: ${type}`);

      return;
    }
  }

  static getConfig(type: WidgetType) {
    const config = WidgetFactory.widgetConfigMap.get(type);

    if (config) {
      return config;
    } else {
      log.error(`Widget config is not registered for type: ${type}`);

      return;
    }
  }

  static getConfigs = () => {
    return Object.fromEntries(WidgetFactory.widgetConfigMap);
  };

  static createWidget(
    widgetData: CanvasWidgetStructure,
    renderMode: RenderMode,
  ): React.ReactNode {
    const { type } = widgetData;

    const builder = WidgetFactory.widgetBuilderMap.get(type);

    if (builder) {
      const widgetProps = {
        key: widgetData.widgetId,
        isVisible: true,
        ...widgetData,
        renderMode,
      };

      return builder(widgetProps);
    } else {
      const ex: WidgetCreationException = {
        message:
          "Widget Builder not registered for widget type" + widgetData.type,
      };
      log.error(ex);
      return null;
    }
  }

  @memoize
  @freeze
  static getWidgetTypes(): WidgetType[] {
    return Array.from(WidgetFactory.widgetsMap.keys());
  }

  @memoize
  @freeze
  static getWidgetDerivedPropertiesMap(
    widgetType: WidgetType,
  ): DerivedPropertiesMap {
    const widget = WidgetFactory.widgetsMap.get(widgetType);

    const derivedProperties = widget?.getDerivedPropertiesMap();

    if (derivedProperties) {
      return derivedProperties;
    } else {
      log.error(
        `Derived properties are not defined for widget type: ${widgetType}`,
      );

      return {};
    }
  }

  @memoize
  @freeze
  static getWidgetDefaultPropertiesMap(
    widgetType: WidgetType,
  ): Record<string, string> {
    const widget = WidgetFactory.widgetsMap.get(widgetType);

    const defaultProperties = widget?.getDefaultPropertiesMap();

    if (defaultProperties) {
      return defaultProperties;
    } else {
      log.error(
        `Default properties are not defined for widget type: ${widgetType}`,
      );
      return {};
    }
  }

  @memoize
  @freeze
  static getWidgetMetaPropertiesMap(
    widgetType: WidgetType,
  ): Record<string, unknown> {
    const widget = WidgetFactory.widgetsMap.get(widgetType);

    const metaProperties = widget?.getMetaPropertiesMap();

    if (metaProperties) {
      return metaProperties;
    } else {
      log.error(
        `Meta properties are not defined for widget type: ${widgetType}`,
      );
      return {};
    }
  }

  @memoize
  @freeze
  static getWidgetPropertyPaneCombinedConfig(
    type: WidgetType,
  ): readonly PropertyPaneConfig[] {
    const contentConfig =
      WidgetFactory.getWidgetPropertyPaneContentConfig(type);
    const styleConfig = WidgetFactory.getWidgetPropertyPaneStyleConfig(type);
    return [...contentConfig, ...styleConfig];
  }

  @memoize
  @freeze
  static getWidgetPropertyPaneConfig(
    type: WidgetType,
  ): readonly PropertyPaneConfig[] {
    const widget = WidgetFactory.widgetsMap.get(type);

    const propertyPaneConfig = widget?.getPropertyPaneConfig();

    const features = widget?.getFeatures();

    if (Array.isArray(propertyPaneConfig) && propertyPaneConfig.length > 0) {
      const enhance = flow([
        enhancePropertyPaneConfig,
        convertFunctionsToString,
        addPropertyConfigIds,
        Object.freeze,
      ]);
      const enhancedPropertyPaneConfig = enhance(propertyPaneConfig, features);

      return enhancedPropertyPaneConfig;
    } else {
      const config = WidgetFactory.getWidgetPropertyPaneCombinedConfig(type);

      if (config === undefined) {
        log.error("Widget property pane config not defined", type);
        return [];
      } else {
        return config;
      }
    }
  }

  @memoize
  @freeze
  static getWidgetPropertyPaneContentConfig(
    type: WidgetType,
  ): readonly PropertyPaneConfig[] {
    const widget = WidgetFactory.widgetsMap.get(type);

    const propertyPaneContentConfig = widget?.getPropertyPaneContentConfig();

    const features = widget?.getFeatures();

    if (propertyPaneContentConfig) {
      const enhance = flow([
        enhancePropertyPaneConfig,
        convertFunctionsToString,
        addPropertyConfigIds,
        addSearchConfigToPanelConfig,
        Object.freeze,
      ]);

      const enhancedPropertyPaneContentConfig = enhance(
        propertyPaneContentConfig,
        features,
        PropertyPaneConfigTypes.CONTENT,
        type,
      );

      return enhancedPropertyPaneContentConfig;
    } else {
      return [];
    }
  }

  @memoize
  @freeze
  static getWidgetPropertyPaneStyleConfig(
    type: WidgetType,
  ): readonly PropertyPaneConfig[] {
    const widget = WidgetFactory.widgetsMap.get(type);

    const propertyPaneStyleConfig = widget?.getPropertyPaneStyleConfig();

    const features = widget?.getFeatures();

    if (propertyPaneStyleConfig) {
      const enhance = flow([
        enhancePropertyPaneConfig,
        convertFunctionsToString,
        addPropertyConfigIds,
        addSearchConfigToPanelConfig,
        Object.freeze,
      ]);

      const enhancedPropertyPaneConfig = enhance(
        propertyPaneStyleConfig,
        features,
        PropertyPaneConfigTypes.STYLE,
      );

      return enhancedPropertyPaneConfig;
    } else {
      return [];
    }
  }

  @memoize
  @freeze
  static getWidgetPropertyPaneSearchConfig(
    type: WidgetType,
  ): readonly PropertyPaneConfig[] {
    const config = generatePropertyPaneSearchConfig(
      WidgetFactory.getWidgetPropertyPaneContentConfig(type),
      WidgetFactory.getWidgetPropertyPaneStyleConfig(type),
    );

    if (config) {
      return config;
    } else {
      return [];
    }
  }

  @memoize
  @freeze
  static getWidgetAutoLayoutConfig(type: WidgetType): AutoLayoutConfig {
    const widget = WidgetFactory.widgetsMap.get(type);

    const baseAutoLayoutConfig = widget?.getAutoLayoutConfig();

    if (baseAutoLayoutConfig) {
      return {
        ...baseAutoLayoutConfig,
        widgetSize:
          baseAutoLayoutConfig.widgetSize?.map((sizeConfig) => ({
            ...sizeConfig,
            configuration: (props: WidgetProps) => {
              if (!props)
                return {
                  minWidth:
                    WidgetFactory.widgetConfigMap.get(type)?.minWidth ||
                    FILL_WIDGET_MIN_WIDTH,
                  minHeight:
                    WidgetFactory.widgetConfigMap.get(type)?.minHeight || 80,
                };
              return sizeConfig.configuration(props);
            },
          })) || [],
        autoDimension: baseAutoLayoutConfig.autoDimension ?? {},
        disabledPropsDefaults: baseAutoLayoutConfig.disabledPropsDefaults ?? {},
      };
    } else {
      log.error(`Auto layout config is not defined for widget type: ${type}`);
      return {
        autoDimension: {},
        widgetSize: [],
        disableResizeHandles: {},
        disabledPropsDefaults: {},
      };
    }
  }

  @memoize
  @freeze
  static getWidgetTypeConfigMap(): WidgetTypeConfigMap {
    const typeConfigMap: WidgetTypeConfigMap = {};
    WidgetFactory.getWidgetTypes().forEach((type) => {
      typeConfigMap[type] = {
        defaultProperties: WidgetFactory.getWidgetDefaultPropertiesMap(type),
        derivedProperties: WidgetFactory.getWidgetDerivedPropertiesMap(type),
        metaProperties: WidgetFactory.getWidgetMetaPropertiesMap(type),
      };
    });
    return typeConfigMap;
  }

  static getAutocompleteDefinitions(
    type: WidgetType,
  ): AutocompletionDefinitions {
    const widget = WidgetFactory.widgetsMap.get(type);

    const autocompleteDefinition = widget?.getAutocompleteDefinitions();

    if (autocompleteDefinition) {
      return autocompleteDefinition;
    } else {
      log.error(
        `Auto complete definitions are not defined for widget type: ${type}`,
      );
      return {};
    }
  }

  @memoize
  @freeze
  static getWidgetSetterConfig(type: WidgetType): Partial<SetterConfig> {
    const widget = WidgetFactory.widgetsMap.get(type);

    const setterConfig = widget?.getSetterConfig() || {};

    return setterConfig;
  }

  @memoize
  @freeze
  static getLoadingProperties(type: WidgetType): Array<RegExp> | undefined {
    const widget = WidgetFactory.widgetsMap.get(type);

    return widget?.getLoadingProperties();
  }

  @memoize
  @freeze
  static getWidgetStylesheetConfigMap(widgetType: WidgetType) {
    const widget = WidgetFactory.widgetsMap.get(widgetType);

    const stylesheet = widget?.getStylesheetConfig();

    if (stylesheet) {
      return stylesheet;
    } else {
      log.error(
        `stylesheet config is not defined for widget type: ${widgetType}`,
      );
      return undefined;
    }
  }

  @memoize
  static getWidgetMethods(type: WidgetType): WidgetMethods {
    const widget = WidgetFactory.widgetsMap.get(type);

    const methods = widget?.getMethods();

    if (methods) {
      return methods;
    } else {
      return {};
    }
  }
}

export type WidgetTypeConfigMap = Record<
  string,
  {
    defaultProperties: Record<string, string>;
    metaProperties: Record<string, any>;
    derivedProperties: WidgetDerivedPropertyType;
  }
>;

export interface WidgetCreationException {
  message: string;
}

export default WidgetFactory;
