import * as ts from 'typescript';
import { parse as babelParse, transformSync, ParserOptions } from '@babel/core';
import transformer from '@compiled/ts-transform-css-in-js';
import { TransformerOptions } from '@compiled/ts-transform-css-in-js/dist/types';

/**
 * Using a stub program means type checking won't work - of course.
 */
const stubProgam: ts.Program = ({
  getTypeChecker: () => ({
    getSymbolAtLocation: () => undefined,
  }),
} as never) as ts.Program;

export default function compiledBabelPlugin(_: any, opts: TransformerOptions = {}) {
  return {
    parserOverride(code: string, parserOpts: ParserOptions, parse: typeof babelParse) {
      if (code.indexOf('@compiled/css-in-js') === -1) {
        // Bail early if there is no work to do.
        return parse(code, parserOpts);
      }

      const userLandFlowPlugin = (parserOpts.plugins || []).find((plugin) => {
        return plugin === 'flow' || plugin[0] === 'flow';
      });

      let parsedCode: string = code;

      if (userLandFlowPlugin) {
        // If the userland Babel config is Flow we need to strip it before passing it to the TypeScript
        // transformer else it'll blow up because TypeScript doesn't support Flow syntax.
        // We're going down a slippery slope - later we might want to investigate a re-write to Babel.
        // See: https://github.com/atlassian-labs/compiled-css-in-js/issues/196
        parsedCode =
          transformSync(parsedCode, {
            babelrc: false,
            configFile: false,
            plugins: ['@babel/plugin-syntax-jsx', '@babel/plugin-transform-flow-strip-types'],
          })?.code || parsedCode;
      }

      const transformedCode = ts.transpileModule(parsedCode, {
        transformers: { before: [transformer(stubProgam, { options: opts })] },
        compilerOptions: {
          module: ts.ModuleKind.ESNext,
          target: ts.ScriptTarget.ESNext,
          jsx: ts.JsxEmit.Preserve,
        },
      });

      return parse(transformedCode.outputText, parserOpts);
    },
  };
}
