import path from 'path';
import { createWriteStream } from 'fs';
import fs from 'fs/promises';
import parseJSON from 'parse-json';
import stripBom from 'strip-bom';
import defaultFromEvent from 'promise-toolbox/fromEvent';
import zipDir from 'zip-dir';
import defaultSourceWatcher from '../watcher.js';
import getValidatedManifest, { getManifestId } from '../util/manifest.js';
import { prepareArtifactsDir } from '../util/artifacts.js';
import { createLogger } from '../util/logger.js';
import { UsageError, isErrorWithCode } from '../errors.js';
import { createFileFilter as defaultFileFilterCreator } from '../util/file-filter.js';
const log = createLogger(import.meta.url);
const DEFAULT_FILENAME_TEMPLATE = '{name}-{version}.zip';
export function safeFileName(name) {
  return name.toLowerCase().replace(/[^a-z0-9.-]+/g, '_');
}

// defaultPackageCreator types and implementation.

// This defines the _locales/messages.json type. See:
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Internationalization#Providing_localized_strings_in__locales

export async function getDefaultLocalizedName({
  messageFile,
  manifestData
}) {
  let messageData;
  let messageContents;
  let extensionName = manifestData.name;
  try {
    messageContents = await fs.readFile(messageFile, {
      encoding: 'utf-8'
    });
  } catch (error) {
    throw new UsageError(`Error reading messages.json file at ${messageFile}: ${error}`);
  }
  messageContents = stripBom(messageContents);
  const {
    default: stripJsonComments
  } = await import('strip-json-comments');
  try {
    messageData = parseJSON(stripJsonComments(messageContents));
  } catch (error) {
    throw new UsageError(`Error parsing messages.json file at ${messageFile}: ${error}`);
  }
  extensionName = manifestData.name.replace(/__MSG_([A-Za-z0-9@_]+?)__/g, (match, messageName) => {
    if (!(messageData[messageName] && messageData[messageName].message)) {
      const error = new UsageError(`The locale file ${messageFile} ` + `is missing key: ${messageName}`);
      throw error;
    } else {
      return messageData[messageName].message;
    }
  });
  return Promise.resolve(extensionName);
}

// https://stackoverflow.com/a/22129960
export function getStringPropertyValue(prop, obj) {
  const properties = prop.split('.');
  const value = properties.reduce((prev, curr) => prev && prev[curr], obj);
  if (!['string', 'number'].includes(typeof value)) {
    throw new UsageError(`Manifest key "${prop}" is missing or has an invalid type: ${value}`);
  }
  const stringValue = `${value}`;
  if (!stringValue.length) {
    throw new UsageError(`Manifest key "${prop}" value is an empty string`);
  }
  return stringValue;
}
function getPackageNameFromTemplate(filenameTemplate, manifestData) {
  const packageName = filenameTemplate.replace(/{([A-Za-z0-9._]+?)}/g, (match, manifestProperty) => {
    return safeFileName(getStringPropertyValue(manifestProperty, manifestData));
  });

  // Validate the resulting packageName string, after interpolating the manifest property
  // specified in the template string.
  const parsed = path.parse(packageName);
  if (parsed.dir) {
    throw new UsageError(`Invalid filename template "${filenameTemplate}". ` + `Filename "${packageName}" should not contain a path`);
  }
  if (!['.zip', '.xpi'].includes(parsed.ext)) {
    throw new UsageError(`Invalid filename template "${filenameTemplate}". ` + `Filename "${packageName}" should have a zip or xpi extension`);
  }
  return packageName;
}
export async function defaultPackageCreator({
  manifestData,
  sourceDir,
  fileFilter,
  artifactsDir,
  overwriteDest,
  showReadyMessage,
  filename = DEFAULT_FILENAME_TEMPLATE
}, {
  fromEvent = defaultFromEvent
} = {}) {
  let id;
  if (manifestData) {
    id = getManifestId(manifestData);
    log.debug(`Using manifest id=${id || '[not specified]'}`);
  } else {
    manifestData = await getValidatedManifest(sourceDir);
  }
  const buffer = await zipDir(sourceDir, {
    filter: (...args) => fileFilter.wantFile(...args)
  });
  let filenameTemplate = filename;
  let {
    default_locale
  } = manifestData;
  if (default_locale) {
    default_locale = default_locale.replace(/-/g, '_');
    const messageFile = path.join(sourceDir, '_locales', default_locale, 'messages.json');
    log.debug('Manifest declared default_locale, localizing extension name');
    const extensionName = await getDefaultLocalizedName({
      messageFile,
      manifestData
    });
    // allow for a localized `{name}`, without mutating `manifestData`
    filenameTemplate = filenameTemplate.replace(/{name}/g, extensionName);
  }
  const packageName = safeFileName(getPackageNameFromTemplate(filenameTemplate, manifestData));
  const extensionPath = path.join(artifactsDir, packageName);

  // Added 'wx' flags to avoid overwriting of existing package.
  const stream = createWriteStream(extensionPath, {
    flags: 'wx'
  });
  stream.write(buffer, () => {
    stream.end();
  });
  try {
    await fromEvent(stream, 'close');
  } catch (error) {
    if (!isErrorWithCode('EEXIST', error)) {
      throw error;
    }
    if (!overwriteDest) {
      throw new UsageError(`Extension exists at the destination path: ${extensionPath}\n` + 'Use --overwrite-dest to enable overwriting.');
    }
    log.info(`Destination exists, overwriting: ${extensionPath}`);
    const overwriteStream = createWriteStream(extensionPath);
    overwriteStream.write(buffer, () => {
      overwriteStream.end();
    });
    await fromEvent(overwriteStream, 'close');
  }
  if (showReadyMessage) {
    log.info(`Your web extension is ready: ${extensionPath}`);
  }
  return {
    extensionPath
  };
}

// Build command types and implementation.

export default async function build({
  sourceDir,
  artifactsDir,
  asNeeded = false,
  overwriteDest = false,
  ignoreFiles = [],
  filename = DEFAULT_FILENAME_TEMPLATE
}, {
  manifestData,
  createFileFilter = defaultFileFilterCreator,
  fileFilter = createFileFilter({
    sourceDir,
    artifactsDir,
    ignoreFiles
  }),
  onSourceChange = defaultSourceWatcher,
  packageCreator = defaultPackageCreator,
  showReadyMessage = true
} = {}) {
  const rebuildAsNeeded = asNeeded; // alias for `build --as-needed`
  log.info(`Building web extension from ${sourceDir}`);
  const createPackage = () => packageCreator({
    manifestData,
    sourceDir,
    fileFilter,
    artifactsDir,
    overwriteDest,
    showReadyMessage,
    filename
  });
  await prepareArtifactsDir(artifactsDir);
  const result = await createPackage();
  if (rebuildAsNeeded) {
    log.info('Rebuilding when files change...');
    onSourceChange({
      sourceDir,
      artifactsDir,
      onChange: () => {
        return createPackage().catch(error => {
          log.error(error.stack);
          throw error;
        });
      },
      shouldWatchFile: (...args) => fileFilter.wantFile(...args)
    });
  }
  return result;
}
//# sourceMappingURL=build.js.map