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

/**
 * resources のディレクトリのパス
 */
export const ResourcesDirectory = normalize(join(__dirname, app.isPackaged ? '../..' : '../resources'));
/**
 * main.jsが格納されているディレクトリ
 */
export const AppDirectory = normalize(__dirname);

/**
 * 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);
  },
  makeTmpProjectDirectory() {
    return impl.makeTmpProject();
  },
  build(e, d) {
    return impl.build(createRendererApi(e), d);
  },
  upload(e, d, p) {
    return impl.upload(createRendererApi(e), d, p);
  },
  getPorts() {
    return impl.getPorts();
  },

  async showOpenFileDialog(evt, option) {
    const win = BrowserWindow.fromWebContents(evt.sender);
    if (win == null) return null;
    const result = await dialog.showOpenDialog(win, {
      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 win = BrowserWindow.fromWebContents(evt.sender);
    if (win == null) return null;
    const result = await dialog.showSaveDialog(win, {
      properties: ['createDirectory'],
      title: option.title,
      defaultPath: option.defaultPath,
      filters: option.filters,
    });
    if (result.canceled || !result.filePath) return null;
    return result.filePath;
  },
});
