import * as parser from '@babel/parser';
import generate from '@babel/generator';
import traverse from '@babel/traverse';
import * as t from '@babel/types';
import { JSExpression, JSFunction, isJSExpression, isJSFunction } from '@alilc/lowcode-types';
import { CodeGeneratorError, IScope } from '../types';
import { transformExpressionLocalRef, ParseError } from './expressionParser';

function parseFunction(content: string): t.FunctionExpression | null {
  try {
    const ast = parser.parse(`(${content});`);
    let resultNode: t.FunctionExpression | null = null;
    traverse(ast, {
      FunctionExpression(path) {
        resultNode = path.node;
        path.stop();
      },
    });
    return resultNode;
  } catch (e) {
    throw new ParseError(content, e);
  }
}

function transformFuncExpr2MethodMember(methodName: string, content: string): string {
  const funcNode = parseFunction(content);
  if (funcNode) {
    const targetNode = t.classMethod(
      'method',
      (methodName && t.identifier(methodName)) || funcNode.id || t.identifier('notDefineName'),
      funcNode.params,
      funcNode.body,
      undefined,
      undefined,
      undefined,
      funcNode.async || undefined,
    );

    const { code: resultCode } = generate(targetNode, { sourceMaps: false });
    return resultCode;
  }

  throw new Error('Can not find Function Statement');
}

function getArrowFunction(content: string) {
  const funcNode = parseFunction(content);
  if (funcNode) {
    const targetNode = t.arrowFunctionExpression(
      funcNode.params,
      funcNode.body,
      funcNode.async || undefined,
    );

    const { code: resultCode } = generate(targetNode, { sourceMaps: false });
    return resultCode;
  }

  throw new Error('Can not find Function Statement');
}

function getBodyStatements(content: string) {
  const funcNode = parseFunction(content);
  if (funcNode) {
    const statements: t.Statement[] = funcNode.body.body;

    const targetNode = t.program(statements, undefined, 'module', undefined);

    const { code: resultCode } = generate(targetNode, { sourceMaps: false });
    return resultCode;
  }

  throw new Error('Can not find Function Statement');
}

export function isJsCode(value: unknown): boolean {
  return isJSExpression(value) || isJSFunction(value);
}

export function generateExpression(value: any, scope: IScope): string {
  if (isJSExpression(value)) {
    const exprVal = (value as JSExpression).value.trim();
    if (!exprVal) {
      return 'null';
    }

    const afterProcessWithLocals = transformExpressionLocalRef(exprVal, scope);
    return afterProcessWithLocals;
  }

  throw new CodeGeneratorError('Not a JSExpression');
}

export function generateFunction(
  value: any,
  config: {
    name?: string;
    isMember?: boolean;
    isBlock?: boolean;
    isArrow?: boolean;
    isBindExpr?: boolean;
  } = {
    name: undefined,
    isMember: false,
    isBlock: false,
    isArrow: false,
    isBindExpr: false,
  },
) {
  if (isJsCode(value)) {
    const functionCfg = value as JSFunction;
    if (config.isMember) {
      return transformFuncExpr2MethodMember(config.name || '', functionCfg.value);
    }
    if (config.isBlock) {
      return getBodyStatements(functionCfg.value);
    }
    if (config.isArrow) {
      return getArrowFunction(functionCfg.value);
    }
    if (config.isBindExpr) {
      return `(${functionCfg.value}).bind(this)`;
    }
    return functionCfg.value;
  }

  throw new CodeGeneratorError('Not a JSFunction or JSExpression');
}
