import { express, time, queryString, value, fsPath, fs } from '../common';
import { Manifest } from './Manifest';
import { IManifest, IManifestResponse } from './types';
import * as util from './util';

export type IManifestQuery = {
  path: string;
  markdown?: boolean;
  yaml?: boolean;
  json?: boolean;
};

export type IResourceQuery = {
  parse?: boolean;
};

/**
 * Initializes a new manifest router.
 */
export function init(args: {
  rootDir: string;
  manifestPath?: string;
  resourcePath?: string;
}) {
  const {
    rootDir,
    manifestPath = '/manifest',
    resourcePath = '/resource',
  } = args;
  const router = express.router();

  /**
   * GET
   * Retrieves a manifest listing of the files at the given path.
   */
  router.get(manifestPath, async (req, res) => {
    const timer = time.timer();
    const query = req.query as IManifestQuery;
    const path = query.path as string;

    let loadExtensions: string[] = [];
    const loadFlag = (...flag: string[]) => {
      if (queryString.isFlag(flag, query)) {
        loadExtensions = [...loadExtensions, ...flag];
      }
    };
    loadFlag('markdown', 'md');
    loadFlag('json');
    loadFlag('yaml', 'yml');

    const done = (
      status: number,
      args: { error?: string; manifest?: IManifest },
    ) => {
      const payload = value.deleteUndefined<IManifestResponse>({
        status,
        elapsed: timer.elapsed(),
        error: args.error ? { message: args.error } : undefined,
        manifest: args.manifest,
      });
      res.status(status).send(payload);
    };

    if (!path) {
      const error = `A '?path=<...>' was not passed in the URL's querystring.`;
      return done(400, { error });
    }

    const manifest = new Manifest({
      rootDir,
      path,
      urlPrefix: resourcePath,
    });

    return done(200, {
      manifest: await manifest.toObject({ loadExtensions }),
    });
  });

  /**
   * GET
   * Retrieves the specified resource path from the manifest.
   */
  const RESOURCE_ROUTE = fsPath.join(resourcePath.replace(/\/\*$/, ''), '*');
  router.get(RESOURCE_ROUTE, async (req, res) => {
    try {
      const path = (req.params['0'] || '') as string;
      const file = fsPath.resolve(fsPath.join(rootDir, path));
      const exists = file ? await fs.pathExists(file) : false;
      const query = req.query as IResourceQuery;

      let data: any;
      const ext = fsPath.extname(path).toLowerCase();

      if (['.yml', '.yaml'].includes(ext)) {
        const res = await util.parseYamlFile(file);
        if (res.error) {
          throw new Error(res.error.message);
        }
        data = res.data;
      }

      if (
        ['.md', '.markdown'].includes(ext) &&
        queryString.isFlag('parse', query)
      ) {
        const res = await util.parseMarkdownFile(file);
        if (res.error) {
          throw new Error(res.error.message);
        }
        data = res.data;
      }

      return file && exists
        ? data
          ? res.send(data)
          : res.sendFile(file)
        : res.status(404).send({
            status: 404,
            error: { message: 'Resource not found.', path },
          });
    } catch (err) {
      const status = 500;
      const message = `Failed to retrieve resource. ${err.message}`;
      res.status(status).send({ status, error: { message } });
    }
  });

  // Finish up.
  return router;
}
