import { get as _get } from 'lodash-es';
import { ICodeResource, ECodeType } from '@cloudbase/cals';
import {
  evalCodeCodules,
  IEvalCodeModulOptions,
  LowCodeModuleParser,
  LowcodeContext,
  WidgetsContext,
} from './parser/base';
import {
  StateParser,
  ComputedParser,
  LifeCycleParser,
  HandlerParser,
  NormalModuleParser,
  RendererParser,
} from './parser/index';

export { generateStyleModule, generateStyleModuleMap } from './parser/lessParser';

const moduleParsers: { [codeType: string]: LowCodeModuleParser } = {
  [ECodeType.LIFECYCLE]: new LifeCycleParser(),
  [ECodeType.COMPUTEDN]: new ComputedParser(),
  [ECodeType.STATE]: new StateParser(),
  [ECodeType.NORMAL_MODULE]: new NormalModuleParser(),
  [ECodeType.HANDLER_FN]: new HandlerParser(),
  [ECodeType.RENDERER]: new RendererParser(),
};

interface IErrorObj {
  file: string;
  message: string;
}

// 用于页面内通信
let lastError: IErrorObj[] = [];

function addError(e: Error, file: string) {
  const err = { file, message: e.message || e.stack };
  lastError.push(err);
}

export const lowcodeContext: LowcodeContext = {} as any;
// export const widgetsContext = {} as WidgetsContext;

// Just for debug
// Object.defineProperty(window, '__widgetsContext', {
//   get: () => widgetsContext,
// });

Object.defineProperty(window, '__lowcodeContext', {
  get: () => lowcodeContext,
  set: (value) => {
    console.warn('__lowcodeContext can not been set to another value');
  },
});

function quickClone(json) {
  return JSON.parse(JSON.stringify(json));
}

/**
 * Reexecute all low code when updated
 */
export async function generateLowcodeModules({
  runtimeWindow,
  codeModulesMap = {},
  onDependencyMiss = () => {},
  dependencies = {},
  externals = {},
}: {
  runtimeWindow: Window;
  codeModulesMap: {
    global?: ICodeResource[];
    [pageId: string]: ICodeResource[];
  };
  onDependencyMiss?: () => void;
  dependencies?: object;
  externals?: Record<string, string>;
}): Promise<LowcodeContext> {
  const allModules = {
    global: quickClone(filterCssCodeModules(codeModulesMap.global)),
  };
  for (let pageId in codeModulesMap) {
    let modules =
      pageId === 'global'
        ? codeModulesMap[pageId]
        : codeModulesMap[pageId].map((module) => {
            return {
              ...module,
              path: module.path?.replace?.(/^.*?\//, `${pageId}/`),
            };
          });
    allModules[pageId] = quickClone(filterCssCodeModules(modules));
  }
  const result = await applyLowcode(allModules, {
    dependencies, // convert proxy to plain js object
    onDependencyMiss,
    runtimeWindow,
    externals,
  });

  Object.assign(lowcodeContext, result);
  return lowcodeContext;
}

export async function applyLowcode(allModules, opts: IEvalCodeModulOptions & { onBeforeParse?: Function }) {
  const innerEvalContext: LowcodeContext = {} as any;
  for (const type in moduleParsers) {
    // eslint-disable-line
    moduleParsers[type].onStart(innerEvalContext);
  }

  opts.onBeforeParse?.(innerEvalContext);
  lastError = [];
  // @ts-ignore
  opts.onCodeError = addError;
  const evaledModules = await evalCodeCodules(allModules, opts);

  // eslint-disable-next-line no-restricted-syntax
  for (const pageId in evaledModules) {
    // eslint-disable-line
    evaledModules[pageId].forEach((m) => {
      const parser = moduleParsers[m.codeType];
      if (!parser) {
        if (m.codeType !== 'mp_config') {
          console.error(`Can not handle code module of type ${m.codeType}`);
        }
        return;
      }
      try {
        parser.parseModule(pageId, m);
      } catch (e) {
        console.error('Error parse module', m, e);
      }
    });
  }

  // eslint-disable-next-line no-restricted-syntax
  for (const type in moduleParsers) {
    try {
      moduleParsers[type].onEnd(innerEvalContext);
    } catch (e) {
      console.error('Module parser error', e);
    }
  }
  return innerEvalContext;
}

// 去掉css 代码
function filterCssCodeModules(modules: ICodeResource[] = []) {
  return modules.filter((module) => module.codeType !== ECodeType.STYLE && module.codeType !== ECodeType.THEME);
}

// export function cleanWidgetsContext() {
//   for (let key in widgetsContext) {
//     delete widgetsContext[key];
//   }
// }

export function cleanLowcodeContext() {
  // @ts-ignore For debugging
  Object.keys(lowcodeContext).forEach((key) => {
    delete lowcodeContext[key];
  });
}
