import { get, isArray, isEmpty, isString, set, uniq } from 'lodash';

import type { PageDSL4and5, PageDSL6 } from '../../types/index.js';

const WIDGET_TO_TRIGGER = {
  INPUT_WIDGET: ['onSubmit', 'onTextChanged'],
  TABLE_WIDGET: ['onRowClicked', 'onRowSelected', 'onPageChange', 'onFiltersChanged', 'onSearchTextChanged'],
  GRID_WIDGET: ['onCellClicked'],
  CHART_WIDGET: ['onSelectData'],
  RICH_TEXT_EDITOR_WIDGET: ['onSelectData'],
  DROP_DOWN_WIDGET: ['onOptionChange'],
  BUTTON_WIDGET: ['onClick'],
  FORM_BUTTON_WIDGET: ['onClick'],
  IMAGE_WIDGET: ['onClick'],
  DATE_PICKER_WIDGET: ['onDateSelected'],
  VIDEO_WIDGET: ['onPlay', 'onPause', 'onEnd'],
  RADIO_GROUP_WIDGET: ['onSelectionChange'],
  CHECKBOX_WIDGET: ['onSelectionChange'],
  TABS_WIDGET: ['onTabSelected'],
  CODE_WIDGET: ['onChange'],
  IFRAME_WIDGET: ['onMessageReceived'],
  MAP_WIDGET: ['onMarkerClick'],
  SLIDEOUT_WIDGET: ['onOpen', 'onClose'],
  MODAL_WIDGET: ['onOpen', 'onClose'],
  ICON_WIDGET: ['onClick']
};

// This migration code uses strings instead of variables like CANVAS_WIDGET for two reasons:
// 1. These strings will never change, even if we change the types in the future
// 2. We do want to make these types much stricter in the future, but for now
export const migrate5to6 = (widget: PageDSL4and5): PageDSL6 => {
  if (isArray(widget.dynamicTriggerPathList)) {
    const bindingKeysToRemove: string[] = [];

    // This is a special case we've found where the dynamicTriggerPathList was entirely missing from some properties
    WIDGET_TO_TRIGGER[widget.type]?.forEach((path) => {
      if (
        !isEmpty(widget[path]) &&
        isArray(widget.dynamicTriggerPathList) &&
        !widget.dynamicTriggerPathList.some(({ key }) => key === path)
      ) {
        widget.dynamicTriggerPathList.push({ key: path });
      }
    });

    widget.dynamicTriggerPathList.forEach(({ key }) => {
      // Some apps have duplicate trigger keys, these will get cleaned up later
      if (bindingKeysToRemove.includes(key)) return;

      const value = get(widget, key);
      bindingKeysToRemove.push(key);
      if (isArray(value)) {
        const newEvents = value.map(extractActionFromString);
        set(widget, key, newEvents);
      } else if (isString(value)) {
        const newEvent = extractActionFromString(value, 0);
        set(widget, key, [newEvent]);
      }
    });

    // Remove duplicates
    widget.dynamicTriggerPathList = uniq(widget.dynamicTriggerPathList.map(({ key }) => key)).map((key: string) => ({ key }));
    widget.dynamicBindingPathList = ((widget.dynamicBindingPathList as { key: string }[]) ?? []).filter(({ key }) => {
      return !bindingKeysToRemove.includes(key);
    });
    widget.dynamicPropertyPathList = ((widget.dynamicPropertyPathList as { key: string }[]) ?? []).filter(({ key }) => {
      return !bindingKeysToRemove.includes(key);
    });
  }
  widget.children?.forEach((child: PageDSL4and5) => {
    if (!child) return;

    migrate5to6(child);
  });
  return widget as unknown as PageDSL6;
};

type ActionDef =
  | {
      id: string;
      type: 'executeApi';
      apiNames: string[];
    }
  | {
      id: string;
      type: 'runJs';
      code: string;
    }
  | {
      id: string;
      type: 'navigateTo';
      url: string;
      newWindow: boolean;
      arguments: string;
    }
  | {
      id: string;
      type: 'controlSlideout';
      name: string;
      direction: 'open' | 'close';
    };

const strictlyMatchApiRun = /^\s*\{\{\s*(\w+)\.run\(\)\s*\}\}\s*$/;
const strictlyMatchOpenClose = /^\s*\{\{\s*(\w+)\.(open|close)\(\)\s*\}\}\s*$/;
const DATA_BIND_REGEX_GLOBAL = /{{([\s\S]*?)}}/g;

function extractActionFromString(value: unknown, index: number): ActionDef {
  if (!isString(value)) {
    throw new Error(`Value is not string: ${JSON.stringify(value)}`);
  }
  const matchesApiRun = value.match(strictlyMatchApiRun);
  if (matchesApiRun?.[1]) {
    return {
      id: `id${index}`,
      type: 'executeApi',

      apiNames: [matchesApiRun?.[1]]
    };
  }
  const matchesSlideoutOpenClose = value.match(strictlyMatchOpenClose);
  if (matchesSlideoutOpenClose?.[1]) {
    const slideoutName = matchesSlideoutOpenClose?.[1];
    return {
      id: `id${index}`,
      type: 'controlSlideout',
      name: slideoutName,
      direction: matchesSlideoutOpenClose[2] as 'open' | 'close'
    };
  }
  const codeMatches = [...value.matchAll(DATA_BIND_REGEX_GLOBAL)];
  return {
    id: `id${index}`,
    type: 'runJs',
    code: codeMatches.map((matches) => matches.slice(1).join('')).join('\n')
  };
}
