import * as FS from 'fs';
import * as Path from 'path';
import { execSync, ExecSyncOptions } from 'child_process';

export type ExecPlugin = (analyzedPackage: AnalyzedPackage) => boolean | void;

export interface ExecPluginConfig { }

export class AnalyzedPackage {

  static create(libRoot?: string) {
    const pkgJsonPath = Path.join(process.cwd(), 'package.json');
    const hasPackageJson = FS.existsSync(pkgJsonPath);
    if (hasPackageJson) {
      const pkgJson = JSON.parse(FS.readFileSync(pkgJsonPath, 'utf-8'));
      return new AnalyzedPackage(pkgJson, libRoot);
    }
  }

  readonly name: { scope?: string; name: string };
  readonly execPluginConfig: ExecPluginConfig;

  readonly libRoot: string;
  readonly lernaRoot: string;

  private constructor(public readonly pkgJson: any, libRoot?: string) {
    const nameSplit = pkgJson.name.split('/');
    const scope: string = nameSplit.length > 1 ? nameSplit[0] : '';
    const name: string = nameSplit.pop();
    this.name = { scope, name };
    this.execPluginConfig = (pkgJson.execPluginConfig || {}) as ExecPluginConfig;
    
    this.libRoot = libRoot || process.cwd();
    this.lernaRoot = this.findRoot();
  }

  isExecPluginDefined(pluginName: keyof ExecPluginConfig) {
    return !!this.execPluginConfig[pluginName]
  }

  execPluginValue<T extends keyof ExecPluginConfig>(pluginName: T): ExecPluginConfig[T] {
    return this.execPluginConfig[pluginName]
  }

  pathJoin(from: 'libRoot' | 'lernaRoot', ...segments: string[]) {
    return Path.join(from === 'libRoot' ? this.libRoot : this.lernaRoot, ...segments);
  }

  binaryPath(from: 'libRoot' | 'lernaRoot', binName: string) {
    return this.pathJoin(from, 'node_modules', '.bin', binName)
  }

  cloneParent(depth = 1) {
    let libRoot = this.libRoot;
    for (let i = 0; i < depth; i++) {
      libRoot = Path.dirname(libRoot);
    }
    return AnalyzedPackage.create(libRoot);
  }

  cloneChildren(...segments: string[]) {
    let libRoot = Path.join(this.libRoot, ...segments);
    return AnalyzedPackage.create(libRoot);
  }

  private findRoot() {
    if (process.env.LERNA_ROOT_PATH) {
      return process.env.LERNA_ROOT_PATH;
    }
    const rel = Path.relative(process.cwd(), __dirname);
    const relSplit = rel.split(Path.sep).filter( p => p === '.' || p === '..' );
    return Path.join(process.cwd(), ...relSplit);
  }
}

const PLUGINS  = new Map<keyof ExecPluginConfig, ExecPlugin>();

export function registerPlugin(key: keyof ExecPluginConfig, plugin: ExecPlugin) {
  PLUGINS.set(key, plugin);
}

export function hasPlugin(key: string): key is keyof ExecPluginConfig {
  return PLUGINS.has(key as any);
}

export function pluginExecuter() {
  const analyzed = AnalyzedPackage.create();
  return (key: keyof ExecPluginConfig): ReturnType<ExecPlugin> => {
    if (analyzed.isExecPluginDefined(key)) {
      console.log(`Exec Plugin: ${key}`);
      return PLUGINS.get(key)(analyzed);
    }
  }
}

export function execute(cmd: string, options?: ExecSyncOptions) {
  execSync(cmd, { ...(options || {}), stdio: 'inherit' } );
}