export function parseCSV(text: string): string[][] {
  //  Works on RFC 4180 csv
  let rows = splitCsvBlock(text);
  if (rows.length === 1) {
    rows = splitCsvBlock(text, '\n');
  }
  return rows.map(splitCsvLine);
}

export function generateCSV(matrix: unknown[][]): string {
  // Generates RFC 4180 csv
  const formattedRows = getFormattedRows(matrix);
  return formattedRows.join('\r\n');
}

function splitCsvBlock(text: string, splitter: string = '\r\n'): string[] {
  if (!text.endsWith(splitter)) {
    text += splitter;
  }
  const lines = [];
  let line = '';
  let inDq = false;

  for (let i = 0; i <= text.length; i++) {
    const c = text[i];

    if (
      c === '"' &&
      ((c[i + 1] === '"' && c[i + 2] === '"') || c[i + 1] !== '"')
    ) {
      inDq = !inDq;
    }

    const isEnd = [...splitter]
      .slice(1)
      .map((s, j) => text[i + j + 1] === s)
      .every(Boolean);

    if (!inDq && c === splitter[0] && isEnd) {
      lines.push(line);
      line = '';
      i = i + splitter.length - 1;
      continue;
    }

    line += c;
  }

  return lines;
}

export function splitCsvLine(line: string): string[] {
  line += ',';

  const items = [];
  let item = '';
  let inDq = false;

  for (let i = 0; i < line.length; i++) {
    const c = line[i];

    if (
      c === '"' &&
      ((c[i + 1] === '"' && c[i + 2] === '"') || c[i + 1] !== '"')
    ) {
      inDq = !inDq;
    }

    if (!inDq && c === ',') {
      item = unwrapDq(item);
      item = item.replaceAll('""', '"');
      items.push(item);
      item = '';
      continue;
    }

    item += c;
  }

  return items;
}

function unwrapDq(item: string): string {
  const s = item.at(0);
  const e = item.at(-1);
  if (s === '"' && e === '"') {
    return item.slice(1, -1);
  }

  return item;
}

function getFormattedRows(matrix: unknown[][]): string[] {
  const formattedMatrix: string[] = [];
  for (const row of matrix) {
    const formattedRow: string[] = [];
    for (const item of row) {
      const formattedItem = getFormattedItem(item);
      formattedRow.push(formattedItem);
    }
    formattedMatrix.push(formattedRow.join(','));
  }

  return formattedMatrix;
}

function getFormattedItem(item: unknown): string {
  if (typeof item === 'string') {
    return formatStringToCSV(item);
  }

  if (item === null || item === undefined) {
    return '';
  }

  if (typeof item === 'object') {
    return item.toString();
  }

  return String(item);
}

function formatStringToCSV(item: string): string {
  let shouldDq = false;
  if (item.match(/^".*"$/)) {
    shouldDq = true;
    item = item.slice(1, -1);
  }

  if (item.match(/"/)) {
    shouldDq = true;
    item = item.replaceAll('"', '""');
  }

  if (item.match(/,|\s/)) {
    shouldDq = true;
  }

  if (shouldDq) {
    return '"' + item + '"';
  }

  return item;
}
