/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const {stableHash} = require('metro-cache');

import type Transformer, {
  JsTransformOptions,
  JsTransformerConfig,
} from '../JSTransformer/worker';
import type {TransformResult} from './types.flow';
import type {LogEntry} from 'metro-core/src/Logger';

export type {
  JsTransformOptions as TransformOptions,
} from '../JSTransformer/worker';

export type Worker = {|
  +transform: typeof transform,
|};

export type TransformerFn = (
  string,
  Buffer,
  JsTransformOptions,
) => Promise<TransformResult<>>;

export type TransformerConfig = {
  transformerPath: string,
  transformerConfig: JsTransformerConfig,
};

type Data = $ReadOnly<{|
  result: TransformResult<>,
  sha1: string,
  transformFileStartLogEntry: LogEntry,
  transformFileEndLogEntry: LogEntry,
|}>;

const transformers: {[string]: Transformer} = {};

function getTransformer(
  projectRoot: string,
  {transformerPath, transformerConfig}: TransformerConfig,
): Transformer {
  const transformerKey = stableHash([
    projectRoot,
    transformerPath,
    transformerConfig,
  ]).toString('hex');

  if (transformers[transformerKey]) {
    return transformers[transformerKey];
  }

  // eslint-disable-next-line lint/flow-no-fixme
  // $FlowFixMe Transforming fixed types to generic types during refactor.
  const Transformer = require(transformerPath);
  transformers[transformerKey] = new Transformer(
    projectRoot,
    transformerConfig,
  );

  return transformers[transformerKey];
}

async function transform(
  filename: string,
  transformOptions: JsTransformOptions,
  projectRoot: string,
  transformerConfig: TransformerConfig,
): Promise<Data> {
  const transformer = getTransformer(projectRoot, transformerConfig);

  const transformFileStartLogEntry = {
    action_name: 'Transforming file',
    action_phase: 'start',
    file_name: filename,
    log_entry_label: 'Transforming file',
    start_timestamp: process.hrtime(),
  };

  const data = fs.readFileSync(path.resolve(projectRoot, filename));
  const sha1 = crypto
    .createHash('sha1')
    .update(data)
    .digest('hex');

  const result = await transformer.transform(filename, data, transformOptions);

  const transformFileEndLogEntry = getEndLogEntry(
    transformFileStartLogEntry,
    filename,
  );

  return {
    result,
    sha1,
    transformFileStartLogEntry,
    transformFileEndLogEntry,
  };
}

function getEndLogEntry(startLogEntry: LogEntry, filename: string): LogEntry {
  const timeDelta = process.hrtime(startLogEntry.start_timestamp);
  const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);

  return {
    action_name: 'Transforming file',
    action_phase: 'end',
    file_name: filename,
    duration_ms,
    log_entry_label: 'Transforming file',
  };
}

((module.exports = {
  transform,
}): Worker);