import { computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import React from 'react';
import propTypes from 'prop-types';
import ReactSelect, { components } from 'react-select';
import ReactSelectCreatable from 'react-select/creatable';

const { Menu } = components;

export class _Select extends React.Component {
  static defaultProps = {
    autoConvertOptions: true,
    menuPortalTarget: document.body,
    menuPlacement: 'auto',
  };

  constructor(props) {
    super(props);
    makeObservable(this, {
      selectedOption: computed,
      options: computed,
    });
  }

  isValidOption(opt) {
    return typeof opt === 'object' && opt.value !== undefined;
  }

  get selectedOption() {
    // eslint-disable-next-line react/prop-types
    const { value, isMulti } = this.props;

    if (isMulti) {
      return this.options.filter((opt) => {
        const values = value ? [].concat(value) : [];

        return values.includes(opt) || values.includes(opt.value);
      });
    }

    return (
      this.options.find((opt) => opt === value || opt.value === value) || null
    );
  }

  get options() {
    // eslint-disable-next-line react/prop-types
    const { autoConvertOptions, options } = this.props;

    if (autoConvertOptions && Array.isArray(options)) {
      // eslint-disable-next-line react/prop-types
      return options.map((opt) => {
        return this.isValidOption(opt)
          ? opt
          : { value: opt, label: String(opt) };
      });
    }

    return options;
  }

  onChange = (value, meta) => {
    if (this.props.onChange) {
      this.props.onChange(value, meta);
    }
  };

  render() {
    const {
      isCreatable,
      components: _components,
      id: inputId,
      ...props
    } = this.props;
    const WrappedMenu = components.Menu ?? Menu;

    const selectProps = {
      ...props,
      ...(inputId ? { inputId } : {}),
      value: this.selectedOption,
      options: this.options,
      onChange: this.onChange,
      className: 'Select',
      classNamePrefix: 'Select',
      components: {
        ..._components,
        Menu: () => <WrappedMenu {...props} />,
      },
    };

    return isCreatable ? (
      <ReactSelectCreatable {...selectProps} /> // select list with ability to add new options
    ) : (
      <ReactSelect {...selectProps} />
    );
  }
}

export const Select = observer(_Select);

class Singleton {
  static instances = new WeakMap();

  static getInstance(_this) {
    return Singleton.instances.get(_this);
  }

  static createInstance(_this, ...args) {
    if (!Singleton.instances.has(this)) {
      Singleton.instances.set(this, new _this(...args));
    }

    return this.getInstance(_this);
  }
}

class Ipc extends Singleton {
  static callbackMap = {};

  static listen = jest.fn().mockImplementation((event, callback) => {
    Ipc.callbackMap[event] = callback;
  });

  static broadcast = jest.fn().mockImplementation((event, payload) => {
    Ipc.callbackMap[event]?.(event, payload);
  });
}

class KubernetesCluster {
  constructor(props) {
    Object.assign(this, props);
  }

  getId() {
    return 'uid';
  }
}

class CatalogEntity {
  constructor(props) {
    Object.assign(this, props);
  }
}

class CatalogCategory {
  constructor(props) {
    Object.assign(this, props);
  }
}

class Spinner extends React.Component {
  static defaultProps = {
    singleColor: true,
    center: false,
  };

  render() {
    const { center, singleColor, ...props } = this.props;

    return (
      <div
        {...props}
        className={`Spinner ${center ? 'center' : ''} ${
          singleColor ? 'singleColor' : ''
        }`}
      />
    );
  }
}

export const Main = {
  K8sApi: {
    KubeObject: null,
    K8sApi: {
      forRemoteCluster: jest.fn(() => ({
        list: jest.fn(() => Promise.resolve([])),
        patch: jest.fn(() => Promise.resolve({})),
      })),
      KubeObject: class KubeObject {},
    },
  },
  Catalog: {
    catalogCategories: {
      add: () => () => {},
    },
  },
};

export const Common = {
  Catalog: {
    KubernetesCluster,
    CatalogEntity,
    CatalogCategory,
    catalogEntities: {
      activeEntity: observable.object({}),
      getItemsForCategory: () => [],
    },
  },
  Store: {
    ExtensionStore: class ExtensionStore extends Singleton {},
  },
  Util: {
    Singleton,
    getAppVersion: () => '1.0.0',
  },
  logger: {
    // eslint-disable-next-line no-console
    info: console.info,
    // eslint-disable-next-line no-console
    error: console.error,
    // eslint-disable-next-line no-console
    warn: console.warn,
    // eslint-disable-next-line no-console
    log: console.log,
  },
  EventBus: {
    appEventBus: {
      emit: () => {},
    },
  },
};

const Button = ({ label, primary, ...props }) => {
  return (
    <button className={primary ? 'primary' : ''} {...props}>
      {label}
    </button>
  );
};

Button.propTypes = {
  label: propTypes.string,
  primary: propTypes.bool,
};

export const Icon = ({ interactive, smallest, ...props }) => {
  const classes = [
    smallest ? 'smallest' : '',
    interactive ? 'interactive' : '',
  ];
  return <i className={classes.join()} {...props} />;
};

Icon.propTypes = {
  interactive: propTypes.bool,
  smallest: propTypes.bool,
};

export const Renderer = {
  Ipc,
  Component: {
    Select,
    Spinner,
    Notifications: {
      error: () => {},
    },
    Button: Button,
    Icon: Icon,
    ConfirmDialog: {
      // eslint-disable-next-line no-undef
      open: jest.fn(),
    },
  },
  Catalog: {
    KubernetesCluster,
    catalogEntities: {
      activeEntity: observable.object({}),
    },
    catalogCategories: {
      getForGroupKind: () => Common.Catalog.KubernetesCluster,
      add: () => () => {},
    },
  },
  K8sApi: {
    KubeObject: class {},
  },
};