import { pathExists, readFileSync, writeFile } from 'fs-extra';
import { isPlainObject } from 'lodash';
import originalSafeEval from 'safe-eval';
import * as path from 'path';
import spawn from 'cross-spawn-promise';
import { DSLType } from './types';

export async function isNPMInstalled(args: {
  workDir: string;
  moduleDir: string;
  npmClient?: string;
}) {
  return pathExists(path.join(args.workDir, 'node_modules'));
}

export async function isNPMModuleInstalled(
  args: { workDir: string; moduleDir: string; npmClient?: string },
  name: string,
) {
  const modulePkgJsonPath = path.resolve(args.workDir, 'node_modules', name, 'package.json');
  return pathExists(modulePkgJsonPath);
}

export async function install(args: { workDir: string; moduleDir: string; npmClient?: string }) {
  if (await isNPMInstalled(args)) return;
  const { workDir, npmClient = 'tnpm' } = args;
  try {
    await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: workDir } as any);
  } catch (e) {
    // TODO
  }
}

export async function installModule(
  args: { workDir: string; moduleDir: string; npmClient?: string },
  name: string,
) {
  if (await isNPMModuleInstalled(args, name)) return;
  const { workDir, npmClient = 'tnpm' } = args;
  try {
    await spawn(npmClient, ['i', name], { stdio: 'inherit', cwd: workDir } as any);
  } catch (e) {
    // TODO
  }
}

export function installTypeDTS(args: {
  workDir: string;
  moduleDir: string;
  npmClient?: string;
  dslType?: DSLType;
}) {
  return installModule(args, `@types/${args.dslType || 'react'}`);
}

export async function installTypeScript(args: {
  workDir: string;
  moduleDir: string;
  npmClient?: string;
}) {
  if (await pathExists(path.join(args.workDir, 'node_modules', '.bin', 'tsc'))) return;
  const { workDir, npmClient = 'tnpm' } = args;
  await spawn(npmClient, ['i', 'typescript'], { stdio: 'inherit', cwd: workDir } as any);
}

export async function installPeerAndDevDeps(args: {
  workDir: string;
  moduleDir: string;
  npmClient?: string;
}) {
  const { workDir, moduleDir, npmClient = 'tnpm' } = args;
  const modulePkgJsonPath = path.resolve(moduleDir, 'package.json');
  if (!(await pathExists(modulePkgJsonPath))) {
    return;
  }
  const pkgJsonPath = path.resolve(workDir, 'package.json');
  if (!(await pathExists(pkgJsonPath))) {
    return;
  }
  const modulePkgJson = await resolvePkgJson(modulePkgJsonPath);
  const pkgJson = await resolvePkgJson(pkgJsonPath);
  const { peerDependencies = {}, devDependencies = {} } = modulePkgJson;
  pkgJson.dependencies = pkgJson.dependencies || {};
  pkgJson.dependencies = {
    ...pkgJson.dependencies,
    ...peerDependencies,
    ...devDependencies,
  };
  await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
  await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: workDir } as any);
}

export async function syncTypeModules(args: {
  workDir: string;
  moduleDir: string;
  npmClient?: string;
}) {
  const { workDir, moduleDir, npmClient = 'tnpm' } = args;
  const pkgJsonPath = path.resolve(moduleDir, 'package.json');
  if (!(await pathExists(pkgJsonPath))) {
    return;
  }
  await installModule(args, 'typesync');
  await spawn(npmClient.replace('m', 'x'), ['typesync'], { stdio: 'inherit', cwd: workDir } as any);
}

export async function resolvePkgJson(pkgJsonPath: string): Promise<{ [k: string]: any }> {
  const content = await loadFile(pkgJsonPath);
  const json = JSON.parse(content);
  return json;
}

export function loadFile(filePath: string): string {
  const content: string | Buffer = readFileSync(filePath);
  if (typeof content === 'string') {
    return content;
  }
  return content.toString();
}

export function isPrimitive(val) {
  return !['object', 'function'].includes(typeof val) || val === null;
}

export function isEvaluable(value) {
  if (isPrimitive(value)) return true;
  if (Array.isArray(value)) {
    return value.every(isEvaluable);
  } else if (isPlainObject(value)) {
    return Object.keys(value).every((key) => isEvaluable(value[key]));
  }
  return false;
}

export function safeEval(value: any) {
  if (typeof value === 'string') return originalSafeEval(value);
  return value;
}
