import { Inject, Injectable, Optional } from '@angular/core';
import extend from 'extend';
import { BixiAuthConfig, BIXI_AUTH_CONFIG } from './config.type';

/**
 * 深度合并对象
 *
 * @param original 原始对象
 * @param arrayProcessMethod 数组处理方式
 *  - `true` 表示替换新值，不管新值为哪种类型
 *  - `false` 表示会合并整个数组（将旧数据与新数据合并成新数组）
 * @param objects 要合并的对象
 */
// tslint:disable-next-line:no-any
function deepMergeKey(original: any, arrayProcessMethod: boolean, ...objects: any[]): any {
  if (Array.isArray(original) || typeof original !== 'object') return original;

  // tslint:disable-next-line:no-any
  const isObject = (v: any) => typeof v === 'object' || typeof v === 'function';

  // tslint:disable-next-line:no-any
  const merge = (target: any, obj: any) => {
    Object.keys(obj)
      .filter(key => key !== '__proto__' && Object.prototype.hasOwnProperty.call(obj, key))
      .forEach(key => {
        const fromValue = obj[key];
        const toValue = target[key];
        if (Array.isArray(toValue)) {
          target[key] = arrayProcessMethod ? fromValue : [...toValue, ...fromValue];
        } else if (fromValue != null && isObject(fromValue) && toValue != null && isObject(toValue)) {
          target[key] = merge(toValue, fromValue);
        } else {
          target[key] = deepCopy(fromValue);
        }
      });
    return target;
  };

  objects.filter(v => v != null && isObject(v)).forEach(v => merge(original, v));

  return original;
}

// tslint:disable-next-line:no-any
function deepCopy(obj: any): any {
  const result = extend(true, {}, { _: obj });
  return result._;
}

@Injectable({ providedIn: 'root' })
export class BixiConfigService {
  private config: BixiAuthConfig;

  constructor(@Optional() @Inject(BIXI_AUTH_CONFIG) defaultConfig?: BixiAuthConfig) {
    this.config = defaultConfig || {};
  }

  get(key?: string): BixiAuthConfig {
    // tslint:disable-next-line:no-any
    const res = ((this.config as { [key: string]: any }) || {}) as any;
    return key ? { [key]: res[key] } : res;
  }

  merge<R>(...defaultValues: R[]): R {
    return deepMergeKey({}, true, ...defaultValues, this.get());
  }

  // tslint:disable-next-line:no-any
  attach<R>(componentThis: any, defaultValues: R) {
    Object.assign(componentThis, this.merge(defaultValues));
  }

  // tslint:disable-next-line:no-any
  attachKey(componentThis: any, key: string) {
    Object.assign(componentThis, this.get(key));
  }

  set(value: BixiAuthConfig): void {
    this.config = { ...this.config, ...value };
  }
}
