import defaultOptions from './default-options';
import getParser from './parsers';

import {
  generateSourceWithDecorators,
  generateSourceWithoutDecorators,
  generateStorySource,
  generateStoriesLocationsMap,
  generateSourcesInExportedParameters,
  generateSourcesInStoryParameters,
} from './generate-helpers';

function extendOptions(source, comments, filepath, options) {
  return {
    ...defaultOptions,
    ...options,
    source,
    comments,
    filepath,
  };
}

function inject(source, filepath, options = {}, log = (message) => {}) {
  const { injectDecorator = true, injectStoryParameters = false } = options;
  const obviouslyNotCode = ['md', 'txt', 'json'].includes(options.parser);
  let parser = null;
  try {
    parser = getParser(options.parser || filepath);
  } catch (e) {
    log(new Error(`(not fatal, only impacting storysource) Could not load a parser (${e})`));
  }

  if (obviouslyNotCode || !parser) {
    return {
      source,
      storySource: {},
      addsMap: {},
      changed: false,
    };
  }
  const ast = parser.parse(source);

  const { changed, source: cleanedSource, comments, exportTokenFound } =
    injectDecorator === true
      ? generateSourceWithDecorators(source, ast)
      : generateSourceWithoutDecorators(source, ast);

  const storySource = generateStorySource(extendOptions(source, comments, filepath, options));
  const newAst = parser.parse(storySource);
  const addsMap = generateStoriesLocationsMap(newAst, []);

  let newSource = cleanedSource;
  if (exportTokenFound) {
    const cleanedSourceAst = parser.parse(cleanedSource);
    if (injectStoryParameters) {
      newSource = generateSourcesInStoryParameters(cleanedSource, cleanedSourceAst, {
        source: storySource,
        locationsMap: addsMap,
      });
    } else {
      newSource = generateSourcesInExportedParameters(cleanedSource, cleanedSourceAst, {
        source: storySource,
        locationsMap: addsMap,
      });
    }
  }

  if (!changed && Object.keys(addsMap || {}).length === 0) {
    return {
      source: newSource,
      storySource,
      addsMap: {},
      changed,
    };
  }

  return {
    source: newSource,
    storySource,
    addsMap,
    changed,
  };
}

export default inject;
