import { MainApi } from '../common/mainApi';
import { RendererApi } from '../common/rendererApi';
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, dialog } from 'electron';
import { keys } from 'ts-transformer-keys';
import * as impl from './impl';

/**
 * RendererプロセスのAPIを呼び出すオブジェクトを作成します
 * @param obj BrowerWindow または IpcMainInvokeEvent
 * @return RendererAPI
 */
export function createRendererApi(obj: BrowserWindow | IpcMainInvokeEvent): RendererApi {
  if (obj instanceof BrowserWindow) {
    return _createRendererApi(obj.webContents);
  } else {
    return _createRendererApi(obj.sender);
  }
}

function _createRendererApi(c: Electron.WebContents): RendererApi {
  // eslint-disable-next-line
  function func(name: string): (...args: any[]) => void {
    return (...args) => c.send(name, ...args);
  }
  // RendererAPIを構築します
  // RendererAPI interfaceから各関数の名前を取得するために
  // ts-transformer-keysを使用しています
  const obj: any = {}; // eslint-disable-line
  for (const name of keys<RendererApi>()) {
    obj[name] = func(name);
  }
  return obj;
}

type MainApiKey = keyof MainApi;
type MainApiFunc<T extends MainApiKey> = MainApi[T];
type MainApiParams<T extends MainApiKey> = Parameters<MainApiFunc<T>>;
type MainApiReturnType<T extends MainApiKey> = ReturnType<MainApiFunc<T>>;
type MainApiHandler<T extends MainApiKey> = _MainApiHandler<
  MainApiParams<T>,
  MainApiHandlerReturnType<MainApiReturnType<T>>
>;
type _MainApiHandler<T extends readonly unknown[], R> = (evt: IpcMainInvokeEvent, ...args: T) => R;
type MainApiHandlerReturnType<T> = T extends PromiseLike<infer U> ? T | U : T;
type ElectronMainApi = { [T in MainApiKey]: MainApiHandler<T> };

/**
 * MainAPIの関数登録
 */
function register(api: ElectronMainApi) {
  for (const name of keys<MainApi>()) {
    ipcMain.handle(name, api[name]);
  }
}

register({
  saveTextFile(evt, filepath, contents) {
    return impl.saveTextFile(createRendererApi(evt), filepath, contents);
  },

  readTextFile(evt, filepath) {
    return impl.readTextFile(createRendererApi(evt), filepath);
  },

  makeTmpDirectory() {
    return impl.makeTmpDirectory();
  },

  compile(evt, filepath, output) {
    return impl.compile(createRendererApi(evt), filepath, output);
  },

  exec(evt, exec) {
    return impl.exec(createRendererApi(evt), exec);
  },

  async showOpenFileDialog(evt, option) {
    const result = await dialog.showOpenDialog(BrowserWindow.getAllWindows()[0], {
      properties: ['openFile'],
      title: option.title,
      defaultPath: option.defaultPath,
      filters: option.filters,
    });
    if (result.canceled) return null;
    return result.filePaths[0];
  },

  async showSaveFileDialog(evt, option) {
    const result = await dialog.showSaveDialog(BrowserWindow.getAllWindows()[0], {
      properties: ['createDirectory'],
      title: option.title,
      defaultPath: option.defaultPath,
      filters: option.filters,
    });
    if (result.canceled || !result.filePath) return null;
    return result.filePath;
  },
});
