import { existsSync } from "fs";
import type { AsyncImporter } from "node-sass";
import EyeglassModule from "../modules/EyeglassModule";
import { URI } from "../util/URI";
import { ImporterFactory } from "./ImporterFactory";
import ImportUtilities from "./ImportUtilities";
import { ROOT_NAME } from "../modules/EyeglassModules";
import { isPresent } from "../util/typescriptUtils";

interface HasAssets {
  name?: string;
  assets?: {
    asAssetImport: (name: string | undefined) => string;
    cacheKey: (name: string) => string;
  };
}

// import pattern matches `assets` and `foo/assets`, but not `foo/bar/assets`
const rAssetsImport = /^(?:([^/]+)\/)?assets$/;
const AssetImporter: ImporterFactory = function (eyeglass, sass, options, fallbackImporter?: AsyncImporter | Array<AsyncImporter>): AsyncImporter {

  return ImportUtilities.createImporter("assets", function(uri, prev, done) {
    let importUtils = new ImportUtilities(eyeglass, sass, options, fallbackImporter, this);

    let isRealFile = existsSync(prev);
    let mod: EyeglassModule | null;

    function importAssetsFor(mod: HasAssets): void {
      let file = "autoGenerated:" + URI.join(mod.name || ROOT_NAME, "assets");
      let contents: () => string;
      function getAssetImport(): string {
        // XXX what is the correct behavior for when mod.name isn't found?
        return mod.assets ? mod.assets.asAssetImport(mod.name) : "";
      }
      // allow build tools to specify a function to cache the imports
      if (isPresent(mod.assets) && isPresent(options.assetsCache)) {
        contents = options.assetsCache.bind(options.assetsCache,
          mod.assets.cacheKey(mod.name || ROOT_NAME), getAssetImport);
      } else {
        contents = getAssetImport;
      }
      importUtils.importOnce({file, contents}, done);
    }

    let isRelativeImport = URI.isRelative(uri);
    let assetsImport = !isRelativeImport && rAssetsImport.exec(uri);
    // the first fragment of the match is the module name
    let moduleName = assetsImport && assetsImport[1];

    // if it's not an assets import, or it's `eyeglass/assets`,
    //  just use the fallback importer
    if (!assetsImport) {
      importUtils.fallback(uri, prev, done, function() {
        done(null);
      });
    } else {
      // if the module name wasn't specified in the import,
      // infer it from the origin
      if (!moduleName && isRealFile) {
        mod = eyeglass.modules.findByPath(prev);
        // if it's an eyeglass module...
        if (mod && mod.isEyeglassModule) {
          // use the module's name
          moduleName = mod.name;
        }
      }

      // if we have a module name...
      if (moduleName) {
        // try to access the module
        mod = eyeglass.modules.access(moduleName, isRealFile ? prev : options.eyeglass.root);
        // if we can access it...
        if (mod) {
          /* eslint max-depth:0 */
          // if it has assets...
          if (mod.assets) {
            // import them
            importAssetsFor(mod);
          } else {
            // otherwise throw an error
            done(new Error("No assets specified for eyeglass plugin " + mod.name));
          }
        } else {
          // if we can't find/access it, throw an error
          done(new Error("No eyeglass plugin named: " + moduleName));
        }
      } else {
        // otherwise, import the app assets
        // TODO: Move app assets to the root module and pass the root eyeglass
        // module here
        importAssetsFor(eyeglass);
      }
    }
  });
};
export default AssetImporter;
