import {
  BuiltInTags,
} from '../tags/BuiltInTags';
import {
  checkPassageAsset,
} from './checkPassageAsset';
import {
  getTag,
} from '../tags/getTag';
import {
  IPassagesMap,
} from './IPassagesMap';
import {
  assert,
  assertValid,
} from 'ts-assertions';

import {
  default as manifest,
  registry,
} from '../../passages/passages-manifest';

type RegistryType = typeof registry;

export const strings = {
  MULTIPLE_DEFAULT_PASSAGES:
    'At least two passages had the tag "start". Only one tag is allowed. ' +
      'The passages found with this error were named %1% and %2%.',

  MULTIPLE_PASSAGES_WITH_SAME_NAME:
    'At least two passages had the name %NAME%. The name property of every ' +
      'passage must be unique.',

  NO_START_PASSAGE:
    'There was no passage in the passages/ folder with the "start" tag. One ' +
      'and only one passage must have the start tag.',

  PASSAGES_MANIFEST_EMPTY:
    'The passages-manifest.json file contained an empty array, indicating ' +
      'that no passages have been authored in passages/.',

  PASSAGES_MANIFEST_INVALID:
    'The passages-manifest.json file was not parseable into an array.',

  PASSAGE_OBJECT_INVALID:
    'One of the passage objects, found at %FILEPATH%, was invalid. ' +
      '%REASON%',
};

assert(Array.isArray(manifest), strings.PASSAGES_MANIFEST_INVALID);
assert(manifest.length, strings.PASSAGES_MANIFEST_EMPTY);

/* Memoize results and return them without computation on repeat calls. */
let passagesMap: IPassagesMap | null = null;
let startPassageName: keyof RegistryType | null = null;

export const getPassagesMapAndStartPassageName = (): {
  passagesMap: IPassagesMap;
  startPassageName: keyof RegistryType;
} => {
  /* Return the memoized results if they exist. */
  if (passagesMap && startPassageName) {
    return {
      passagesMap,
      startPassageName,
    };
  }

  passagesMap = {} as IPassagesMap;

  manifest.forEach(({
    asset,
    filepath,
  }) => {
    try {
      checkPassageAsset(asset);
    } catch (err) {
      const errStr = strings.PASSAGE_OBJECT_INVALID
        .replace('%FILEPATH%', filepath)
        .replace('%REASON%', err);

      throw new Error(errStr);
    }

    const {
      name,
      tags,
    } = asset;

    assert(
      !(name in passagesMap!),
      strings.PASSAGE_OBJECT_INVALID.replace('%NAME%', name),
    );

    if (getTag(tags, BuiltInTags.Start)) {
      /* Throw if a passage with a duplicate name is found. */
      if (startPassageName) {
        throw new Error(
          strings.MULTIPLE_DEFAULT_PASSAGES
            .replace('%1%', startPassageName)
            .replace('%2%', name)
        );
      }

      startPassageName = name;
    }

    if (!getTag(tags, BuiltInTags.NoRender)) {
      /* Do not place NoRender passages in the passage map. */
      passagesMap![name] = asset;
    }
  });

  const safeStartPassageName = assertValid<keyof RegistryType>(
    startPassageName,
    strings.NO_START_PASSAGE,
  );

  return {
    passagesMap,
    startPassageName: safeStartPassageName,
  };
};
