/**
 * This file contains a CLI for Linaria.
 */

import path from 'path';
import fs from 'fs';
import normalize from 'normalize-path';
import mkdirp from 'mkdirp';
import glob from 'glob';
import yargs from 'yargs';
import { transform } from '@linaria/babel-preset';

const { argv } = yargs
  .usage('Usage: $0 [options] <files ...>')
  .option('config', {
    alias: 'c',
    type: 'string',
    description: 'Path to a config file',
    requiresArg: true,
  })
  .option('out-dir', {
    alias: 'o',
    type: 'string',
    description: 'Output directory for the extracted CSS files',
    demandOption: true,
    requiresArg: true,
  })
  .option('source-maps', {
    alias: 's',
    type: 'boolean',
    description: 'Generate source maps for the CSS files',
    default: false,
  })
  .option('source-root', {
    alias: 'r',
    type: 'string',
    description: 'Directory containing the source JS files',
    demandOption: true,
    requiresArg: true,
  })
  .option('insert-css-requires', {
    alias: 'i',
    type: 'string',
    description:
      'Directory containing JS files to insert require statements for the CSS files',
    requiresArg: true,
  })
  .implies('insert-css-requires', 'source-root')
  .option('ignore', {
    alias: 'x',
    type: 'string',
    description: 'Pattern of files to ignore. Be sure to provide a string',
    requiresArg: true,
  })
  .alias('help', 'h')
  .alias('version', 'v')
  .strict();

processFiles(argv._, {
  outDir: argv['out-dir'],
  sourceMaps: argv['source-maps'],
  sourceRoot: argv['source-root'],
  insertCssRequires: argv['insert-css-requires'],
  configFile: argv.config,
  ignore: argv.ignore,
});

type Options = {
  outDir: string;
  sourceMaps?: boolean;
  sourceRoot: string;
  insertCssRequires?: string;
  configFile?: string;
  ignore?: string;
};

function processFiles(files: string[], options: Options) {
  let count = 0;

  const resolvedFiles = files.reduce(
    (acc, pattern) => [
      ...acc,
      ...glob.sync(pattern, { absolute: true, ignore: options.ignore }),
    ],
    [] as string[]
  );

  resolvedFiles.forEach((filename) => {
    const outputFilename = resolveOutputFilename(
      filename,
      options.outDir,
      options.sourceRoot
    );

    const { cssText, sourceMap, cssSourceMapText } = transform(
      fs.readFileSync(filename).toString(),
      {
        filename,
        outputFilename,
        pluginOptions: {
          configFile: options.configFile,
        },
      }
    );

    if (cssText) {
      mkdirp.sync(path.dirname(outputFilename));

      const cssContent =
        options.sourceMaps && sourceMap
          ? `${cssText}\n/*# sourceMappingURL=${outputFilename}.map */`
          : cssText;

      fs.writeFileSync(outputFilename, cssContent);

      if (
        options.sourceMaps &&
        sourceMap &&
        typeof cssSourceMapText !== 'undefined'
      ) {
        fs.writeFileSync(`${outputFilename}.map`, cssSourceMapText);
      }

      if (options.insertCssRequires && options.sourceRoot) {
        const inputFilename = path.resolve(
          options.insertCssRequires,
          path.relative(options.sourceRoot, filename)
        );

        const normalizedInputFilename =
          resolveRequireInsertionFilename(inputFilename);

        const relativePath = normalize(
          path.relative(path.dirname(inputFilename), outputFilename)
        );

        const requireStatement = `\nrequire('${
          relativePath.startsWith('.') ? relativePath : `./${relativePath}`
        }');`;

        const inputContent = fs.readFileSync(normalizedInputFilename, 'utf-8');

        if (!inputContent.trim().endsWith(requireStatement)) {
          fs.writeFileSync(
            normalizedInputFilename,
            `${inputContent}\n${requireStatement}\n`
          );
        }
      }

      count++;
    }
  });

  // eslint-disable-next-line no-console
  console.log(`Successfully extracted ${count} CSS files.`);
}

function resolveRequireInsertionFilename(filename: string) {
  return filename.replace(/\.tsx?/, '.js');
}

function resolveOutputFilename(
  filename: string,
  outDir: string,
  sourceRoot: string
) {
  const outputFolder = path.relative(sourceRoot, path.dirname(filename));
  const outputBasename = path
    .basename(filename)
    .replace(path.extname(filename), '.css');

  return path.join(outDir, outputFolder, outputBasename);
}
