import { fs, fsPath, value } from '../common';
import {
  IManifestResource,
  ManifestFileType,
  IManifestFolder,
  IManifestFile,
  IManifestJson,
  IManifestYaml,
} from './types';
import {
  toManifestImage,
  parseJson,
  parseYaml,
  toManifestMarkdown,
} from './util';

/**
 * Creates an IResource.
 */
export async function toResource(args: {
  rootPath: string;
  filePath: string;
  urlPrefix?: string;
  loadExtensions?: string[];
}): Promise<IManifestResource | undefined> {
  const { rootPath, filePath, urlPrefix, loadExtensions = [] } = args;
  const file = fsPath.parse(filePath);
  let body: string | undefined;
  let error: IManifestResource['error'] | undefined;
  const type = await toFileType(filePath);

  // Load body of document if requested.
  const ext = (file.ext || '').replace(/^\./, '');
  if (loadExtensions.includes(ext)) {
    try {
      body = (await fs.readFile(filePath)).toString();
    } catch (err) {
      const message = `Failed to load body. ${err.message}`;
      error = { message };
    }
  }

  // Resolve path to the file.
  let path = filePath.substr(rootPath.length);
  path = urlPrefix ? fsPath.join(urlPrefix, path) : path;

  // Convert to specific manifest type.
  let result: IManifestResource | undefined;
  switch (type) {
    case 'FOLDER':
      const folder: IManifestFolder = { type: 'FOLDER', path };
      result = folder;
      break;

    case 'FILE':
      const file: IManifestFile = { type: 'FILE', path };
      result = file;
      break;

    case 'FILE/image':
      const image = await toManifestImage(filePath, path, { size: true });
      result = image;
      break;

    case 'FILE/markdown':
      const markdown = await toManifestMarkdown(path, body);
      result = markdown;
      break;

    case 'FILE/json':
      const manifestJson: IManifestJson = {
        type: 'FILE/json',
        path,
        ...parseJson(body),
      };
      result = manifestJson;
      break;

    case 'FILE/yaml':
      const manifestYaml: IManifestYaml = {
        type: 'FILE/yaml',
        path,
        ...parseYaml(body),
      };
      result = manifestYaml;
      break;

    default:
      throw new Error(`File type '${type}' not supported.`);
  }

  // Finish up.
  error = result && result.error ? result.error : error;
  return result ? value.deleteUndefined({ ...result, error }) : undefined;
}

/**
 * Determine the file-type of the given file.
 */
export async function toFileType(path: string): Promise<ManifestFileType> {
  if ((await fs.lstat(path)).isDirectory()) {
    return 'FOLDER';
  }
  const file = fsPath.parse(path);
  const ext = file.ext.toLowerCase();

  if (['.md', '.markdown'].includes(ext)) {
    return 'FILE/markdown';
  }

  if (['.jpg', '.jpeg', '.png', '.svg'].includes(ext)) {
    return 'FILE/image';
  }

  if (['.json'].includes(ext)) {
    return 'FILE/json';
  }

  if (['.yml', '.yaml'].includes(ext)) {
    return 'FILE/yaml';
  }

  return 'FILE';
}
