// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/** Array index or record key corresponding to a value for a data object. */ const QUOTE = '"';
const LF = "\n";
const CRLF = "\r\n";
const BYTE_ORDER_MARK = "\ufeff";
function getEscapedString(value, sep) {
  if (value === undefined || value === null) return "";
  let str = "";
  if (typeof value === "object") str = JSON.stringify(value);
  else str = String(value);
  // Is regex.test more performant here? If so, how to dynamically create?
  // https://stackoverflow.com/questions/3561493/
  if (str.includes(sep) || str.includes(LF) || str.includes(QUOTE)) {
    return `${QUOTE}${str.replaceAll(QUOTE, `${QUOTE}${QUOTE}`)}${QUOTE}`;
  }
  return str;
}
function normalizeColumn(column) {
  let header;
  let prop;
  if (typeof column === "object") {
    if (Array.isArray(column)) {
      header = String(column[column.length - 1]);
      prop = column;
    } else {
      prop = Array.isArray(column.prop) ? column.prop : [
        column.prop
      ];
      header = typeof column.header === "string" ? column.header : String(prop[prop.length - 1]);
    }
  } else {
    header = String(column);
    prop = [
      column
    ];
  }
  return {
    header,
    prop
  };
}
/**
 * Error thrown in {@linkcode stringify}.
 *
 * @example Usage
 * ```ts no-assert
 * import { stringify, StringifyError } from "@std/csv/stringify";
 *
 * try {
 *   stringify([{ a: 1 }, { a: 2 }], { separator: "\r\n" });
 * } catch (error) {
 *   if (error instanceof StringifyError) {
 *     console.error(error.message);
 *   }
 * }
 * ```
 */ export class StringifyError extends Error {
  /**
   * Construct a new instance.
   *
   * @example Usage
   * ```ts no-eval
   * import { StringifyError } from "@std/csv/stringify";
   *
   * throw new StringifyError("An error occurred");
   * ```
   *
   * @param message The error message.
   */ constructor(message){
    super(message);
    this.name = "StringifyError";
  }
}
/**
 * Returns an array of values from an object using the property accessors
 * (and optional transform function) in each column
 */ function getValuesFromItem(item, normalizedColumns) {
  const values = [];
  if (normalizedColumns.length) {
    for (const column of normalizedColumns){
      let value = item;
      for (const prop of column.prop){
        if (typeof value !== "object" || value === null) continue;
        if (Array.isArray(value)) {
          if (typeof prop === "number") value = value[prop];
          else {
            throw new StringifyError('Property accessor is not of type "number"');
          }
        } else value = value[prop];
      }
      values.push(value);
    }
  } else {
    if (Array.isArray(item)) {
      values.push(...item);
    } else if (typeof item === "object") {
      throw new StringifyError("No property accessor function was provided for object");
    } else {
      values.push(item);
    }
  }
  return values;
}
/**
 * Converts an array of objects into a CSV string.
 *
 * @example Usage
 * ```ts
 * import {
 *   Column,
 *   stringify,
 * } from "@std/csv/stringify";
 * import { assertEquals } from "@std/assert/assert-equals";
 *
 * type Character = {
 *   age: number;
 *   name: {
 *     first: string;
 *     last: string;
 *   };
 * };
 *
 * const data: Character[] = [
 *   {
 *     age: 70,
 *     name: {
 *       first: "Rick",
 *       last: "Sanchez",
 *     },
 *   },
 *   {
 *     age: 14,
 *     name: {
 *       first: "Morty",
 *       last: "Smith",
 *     },
 *   },
 * ];
 *
 * let columns: Column[] = [
 *   ["name", "first"],
 *   "age",
 * ];
 *
 * assertEquals(stringify(data, { columns }), `first,age\r\nRick,70\r\nMorty,14\r\n`);
 * ```
 *
 * @param data The source data to stringify. It's an array of items which are
 * plain objects or arrays.
 * @returns A CSV string.
 */ export function stringify(data, { headers = true, separator: sep = ",", columns = [], bom = false } = {}) {
  if (sep.includes(QUOTE) || sep.includes(CRLF)) {
    const message = [
      "Separator cannot include the following strings:",
      '  - U+0022: Quotation mark (")',
      "  - U+000D U+000A: Carriage Return + Line Feed (\\r\\n)"
    ].join("\n");
    throw new StringifyError(message);
  }
  const normalizedColumns = columns.map(normalizeColumn);
  let output = "";
  if (bom) {
    output += BYTE_ORDER_MARK;
  }
  if (headers && normalizedColumns.length > 0) {
    output += normalizedColumns.map((column)=>getEscapedString(column.header, sep)).join(sep);
    output += CRLF;
  }
  for (const item of data){
    const values = getValuesFromItem(item, normalizedColumns);
    output += values.map((value)=>getEscapedString(value, sep)).join(sep);
    output += CRLF;
  }
  return output;
}
//# sourceMappingURL=data:application/json;base64,[encoded data, 14968 characters]
// denoCacheMetadata=558992697234944537,14632836773653337264