import {
  CollectionState,
  ErrorMonitor,
  FiltersMap,
  Fqdn,
  KeyedItem,
  PartialWindow,
  PickerEvents,
  PickerState,
  ReportBI,
  TaskState,
  withoutIrrelevant,
} from '@wix/bex-core';
import { replaceUrlCurlySegments } from '../utils/replaceUrlCurlySegments';
import { action, makeObservable, observable, runInAction } from 'mobx';
import { validateEncodedUrl } from '../utils/validateEncodedUrl';
import { LogoType } from '../hooks';
import {
  cairoCtaClicked,
  newItemCreationEnd,
  newItemCreationStart,
} from '@wix/bex-core/bi';
import { registerMatchMedia } from '../utils/matchMediaQuery';

const withoutDefaults = withoutIrrelevant<
  | 'currentTab'
  | 'currentView'
  | 'currentFilters'
  | 'currentSortOrder'
  | 'listSize'
  | 'filteredListSize'
  | 'maxItems'
  | 'initialItems'
  | 'type'
  | 'componentType'
>();

type OnNavigate<T> = (params: {
  item: T;
  index: number;
  url: string;
}) => unknown;

export interface PickerStandaloneEvents<T, F extends FiltersMap>
  extends PickerEvents<T, F> {
  readonly onSingleItemAutoNavigation?: OnNavigate<T>;
  readonly onContinueToDestinationUrl?: OnNavigate<T>;
}

export interface PickerStandaloneStateParams<
  T,
  F extends FiltersMap = FiltersMap,
> {
  collection: CollectionState<T, F>;
  initialSelect?: true | ((item: T, index: number) => boolean);
  noItemsAvailableDestinationUrl?: string;
  defaultDestinationUrl: string | ((item: T) => string);
  window: PartialWindow;
  errorMonitor: ErrorMonitor;
  logoType: LogoType;
  urlSegmentPropertyMap?: Record<string, (keyof T)[]>;
  events?: PickerStandaloneEvents<T, F>;
  shouldFilterNotAffectTotalCount?: (filterName: keyof F) => boolean;
  createBILogger: <P extends Fqdn>(defaultParams: P) => ReportBI;
}

const supportedProtocols = ['https:', 'http:'];

function tryParseUrl(str: string) {
  try {
    const url = new URL(str);
    validateEncodedUrl(url);

    if (!supportedProtocols.includes(url.protocol)) {
      throw new Error(`Unsupported action url protocol: ${url.protocol}`);
    }

    return url;
  } catch (e) {
    console.error(e);
  }

  return undefined;
}

export class PickerStandaloneState<T, F extends FiltersMap = FiltersMap> {
  readonly picker: PickerState<T, F>;
  readonly initialSelect?: PickerStandaloneStateParams<T, F>['initialSelect'];
  readonly noItemsAvailableDestinationUrl?: string;
  readonly defaultDestinationUrl: string | ((item: T) => string);

  primaryButtonText: string | null;
  title?: string | null | undefined;
  subtitle?: string | null | undefined;
  destinationUrlTemplate?: URL;
  readonly reportBi;
  readonly window: PartialWindow;
  readonly errorMonitor: ErrorMonitor;
  readonly urlSegmentPropertyMap?: Record<string, (keyof T)[]>;
  readonly events: PickerStandaloneEvents<T, F>;

  readonly initTask = new TaskState();

  readonly logoType: PickerStandaloneStateParams<T, F>['logoType'];

  submitted = false;

  isMobileView: boolean;
  isDuringSearch: boolean;

  _hasCreateNewAction?: boolean;

  constructor(params: Readonly<PickerStandaloneStateParams<T, F>>) {
    this.reportBi = params.createBILogger({
      ...params.collection.fqdn,
      type: 'standalone',
      componentType: 'Picker Standalone',
    });

    this.picker = new PickerState({
      getMaxItems: () => 1,
      collection: params.collection,
      shouldFilterNotAffectTotalCount: params.shouldFilterNotAffectTotalCount,
      events: params.events,
      reportBi: this.reportBi,
      loadStartTime: 0, // for standalone we want measure from start of navigation
    });

    this.noItemsAvailableDestinationUrl = params.noItemsAvailableDestinationUrl;
    this.defaultDestinationUrl = params.defaultDestinationUrl;
    this.initialSelect = params.initialSelect;
    this.window = params.window;
    this.errorMonitor = params.errorMonitor;
    this.urlSegmentPropertyMap = params.urlSegmentPropertyMap;
    this.events = params.events ?? {};
    this.logoType = params.logoType;

    const windowUrl = new URL(this.window.location.href);

    this.primaryButtonText = windowUrl.searchParams.get('primaryButtonText');

    this.title = windowUrl.searchParams.get('title')?.trim();

    this.subtitle = windowUrl.searchParams.get('subtitle');

    const maybeActionUrlString = windowUrl.searchParams.get('actionUrl');

    if (maybeActionUrlString) {
      this.destinationUrlTemplate = tryParseUrl(maybeActionUrlString);
    }

    this.isMobileView = !this.window.matchMedia(`(min-width: 768px)`).matches;
    this.isDuringSearch = false;

    makeObservable(this, {
      handlePrimaryButtonClick: action.bound,
      submitted: observable.ref,
      isMobileView: observable.ref,
      isDuringSearch: observable.ref,
      setIsDuringSearch: action,
    });
  }

  onCreateNewClick() {
    this.reportBi(
      withoutDefaults(newItemCreationStart)({
        ...(this.picker._commonDynamicBiParams() as {}),
      }),
    );
  }

  onCreateNewActionCompleted({ itemId }: { itemId?: string }) {
    this.reportBi(
      withoutDefaults(newItemCreationEnd)({
        ...this.picker._commonDynamicBiParams(),
        resultType: 'success',
        itemId,
      }),
    );
  }

  init() {
    const {
      picker,
      initTask,
      noItemsAvailableDestinationUrl,
      initialSelect,
      window,
      errorMonitor,
      events: { onSingleItemAutoNavigation },
    } = this;

    const matchMedia = this.window.matchMedia(`(min-width: 768px)`);
    const matchMediaChangeListener = ({ matches }: { matches: boolean }) => {
      this.isMobileView = !matches;
    };

    const disposeMedia = registerMatchMedia({
      errorMonitor,
      matchMediaChangeListener,
      matchMedia,
    });

    const disposers = [
      picker.init(),
      this.subscribeResultUpdated(),
      disposeMedia,
    ];

    initTask.run(
      async () => {
        await picker.initTask.status.promise;

        await runInAction(async () => {
          const { collection } = picker;

          const {
            result,
            hasAvailableItems,
            bulkSelect,
            query: {
              search: { isEmpty: searchIsEmpty },
            },
          } = collection;

          const { status, keyedItems, total } = result;

          if (
            status.isSuccess &&
            !hasAvailableItems &&
            !this._hasCreateNewAction &&
            noItemsAvailableDestinationUrl
          ) {
            window.location.href = noItemsAvailableDestinationUrl;
            await new Promise(() => {}); // keep showing loader while navigating
            return;
          }

          if (
            status.isSuccess &&
            total === 1 &&
            !this._hasCreateNewAction &&
            searchIsEmpty
          ) {
            const navigated = this.navigateToDestinationUrl(
              keyedItems[0],
              onSingleItemAutoNavigation,
            );
            if (navigated) {
              await new Promise(() => {}); // keep showing loader while navigating
            }

            return;
          }

          if (initialSelect) {
            const selectedItem =
              initialSelect === true
                ? keyedItems[0]
                : keyedItems.find(({ item }, index) =>
                    initialSelect(item, index),
                  );

            if (selectedItem != null) {
              bulkSelect.selectOneItem(selectedItem);
            }
          }
        });
      },
      (error) => {
        errorMonitor.captureException(error);
      },
    );

    return () => {
      for (const disposer of disposers) {
        disposer();
      }
    };
  }

  private resolveDestinationUrl(item: T) {
    const {
      destinationUrlTemplate,
      defaultDestinationUrl,
      urlSegmentPropertyMap,
    } = this;
    if (destinationUrlTemplate) {
      return replaceUrlCurlySegments(
        destinationUrlTemplate,
        item,
        urlSegmentPropertyMap,
      );
    }

    if (typeof defaultDestinationUrl === 'string') {
      const url = tryParseUrl(defaultDestinationUrl);
      if (url) {
        return replaceUrlCurlySegments(url, item, urlSegmentPropertyMap);
      }

      return defaultDestinationUrl;
    }

    return defaultDestinationUrl(item);
  }

  private navigateToDestinationUrl(
    { item, index }: KeyedItem<T>,
    onNavigate: OnNavigate<T> | undefined,
  ) {
    const { window } = this;

    const destinationUrl = this.resolveDestinationUrl(item);

    const url = destinationUrl.toString();

    onNavigate?.({ item, index, url });

    window.location.href = url;

    return true;
  }

  handlePrimaryButtonClick(keyedItem: KeyedItem<T>) {
    const {
      events: { onContinueToDestinationUrl },
      submitted,
      picker,
      reportBi,
    } = this;

    if (submitted) {
      return;
    }

    picker.toggle(keyedItem, { clear: true });

    reportBi(
      cairoCtaClicked({
        ...this.picker._commonDynamicBiParams(),
        url: typeof window !== 'undefined' ? window.location.href : '',
        ctaIndex: 1,
        numCtas: 1,
        location: 'pickerStandalone',
        moreActionsIndex: 0,
        ctaName: 'continue',
      }),
    );

    this.submitted = this.navigateToDestinationUrl(
      keyedItem,
      onContinueToDestinationUrl,
    );
  }

  setIsDuringSearch(newVal: boolean = !this.isDuringSearch) {
    this.isDuringSearch = newVal;
  }

  private subscribeResultUpdated() {
    const onResultUpdated = action(() => {
      const {
        picker: {
          collection: { result, bulkSelect },
        },
      } = this;
      bulkSelect.select.filter((value) => result.has(value.key));
    });

    const {
      picker: { collection },
    } = this;
    collection.emitter.on('resultUpdated', onResultUpdated);

    return () => {
      collection.emitter.off('resultUpdated', onResultUpdated);
    };
  }
}
