import { WidgetBlueprint } from "reducers/entityReducers/widgetConfigReducer";
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
import { WidgetProps } from "widgets/BaseWidget";
import { generateReactKey } from "utils/generators";
import { call } from "redux-saga/effects";

function buildView(view: WidgetBlueprint["view"], widgetId: string) {
  const children = [];
  for (const template of view) {
    //TODO(abhinav): Can we keep rows and size mandatory?
    try {
      children.push({
        widgetId,
        type: template.type,
        leftColumn: template.position.left || 0,
        topRow: template.position.top || 0,
        columns: template.size && template.size.cols,
        rows: template.size && template.size.rows,
        newWidgetId: generateReactKey(),
        props: template.props,
      });
    } catch (e) {
      console.error(e);
    }
  }
  return children;
}

export function* buildWidgetBlueprint(
  blueprint: WidgetBlueprint,
  widgetId: string,
) {
  const widgetProps = yield call(buildView, blueprint.view, widgetId);
  return widgetProps;
}

export type UpdatePropertyArgs = {
  widgetId: string;
  propertyName: string;
  propertyValue: any;
};
export type BlueprintOperationAddActionFn = () => void;
export type BlueprintOperationModifyPropsFn = (
  widget: WidgetProps & { children?: WidgetProps[] },
  parent?: WidgetProps,
) => UpdatePropertyArgs[] | undefined;

export type BlueprintOperationFunction =
  | BlueprintOperationModifyPropsFn
  | BlueprintOperationAddActionFn;

export enum BlueprintOperationTypes {
  MODIFY_PROPS = "MODIFY_PROPS",
  ADD_ACTION = "ADD_ACTION",
}

export type BlueprintOperationType = keyof typeof BlueprintOperationTypes;

export type BlueprintOperation = {
  type: BlueprintOperationType;
  fn: BlueprintOperationFunction;
};

export function* executeWidgetBlueprintOperations(
  operations: BlueprintOperation[],
  widgets: { [widgetId: string]: FlattenedWidgetProps },
  widgetId: string,
) {
  operations.forEach((operation: BlueprintOperation) => {
    switch (operation.type) {
      case BlueprintOperationTypes.MODIFY_PROPS:
        const widget: WidgetProps & { children?: string[] | WidgetProps[] } = {
          ...widgets[widgetId],
        };
        if (widget.children && widget.children.length > 0) {
          widget.children = (widget.children as string[]).map(
            (childId: string) => widgets[childId],
          ) as WidgetProps[];
        }
        const updatePropertyPayloads:
          | UpdatePropertyArgs[]
          | undefined = (operation.fn as BlueprintOperationModifyPropsFn)(
          widget as WidgetProps & { children?: WidgetProps[] },
          widgets[widget.parentId],
        );
        updatePropertyPayloads &&
          updatePropertyPayloads.forEach((params: UpdatePropertyArgs) => {
            widgets[params.widgetId][params.propertyName] =
              params.propertyValue;
          });
    }
  });
  return yield widgets;
}
