'use strict';

const Types = require('../constants/types.js');
const Charsets = require('../constants/charsets.js');
const helpers = require('../helpers');

const typeNames = [];
for (const t in Types) {
  typeNames[Types[t]] = t;
}

function readField({
  packet,
  field,
  type,
  charset,
  encoding,
  config,
  options,
}) {
  const supportBigNumbers = Boolean(
    options.supportBigNumbers || config.supportBigNumbers
  );
  const bigNumberStrings = Boolean(
    options.bigNumberStrings || config.bigNumberStrings
  );
  const timezone = options.timezone || config.timezone;
  const dateStrings = options.dateStrings || config.dateStrings;

  // MariaDB sends JSON values as LONGTEXT; the JSON type is only visible in
  // the extended metadata (MARIADB_CLIENT_EXTENDED_METADATA capability)
  if (field.extendedFormat === 'json') {
    return config.jsonStrings
      ? packet.readLengthCodedString(encoding)
      : packet.parseJson(encoding, supportBigNumbers);
  }

  switch (type) {
    case Types.TINY:
    case Types.SHORT:
    case Types.LONG:
    case Types.INT24:
    case Types.YEAR:
      return packet.parseLengthCodedIntNoBigCheck();
    case Types.LONGLONG:
      if (supportBigNumbers && bigNumberStrings) {
        return packet.parseLengthCodedIntString();
      }
      return packet.parseLengthCodedInt(supportBigNumbers);
    case Types.FLOAT:
    case Types.DOUBLE:
      return packet.parseLengthCodedFloat();
    case Types.NULL:
    case Types.DECIMAL:
    case Types.NEWDECIMAL:
      if (config.decimalNumbers) {
        return packet.parseLengthCodedFloat();
      }
      return packet.readLengthCodedString('ascii');
    case Types.DATE:
      if (helpers.typeMatch(type, dateStrings, Types)) {
        return packet.readLengthCodedString('ascii');
      }
      return packet.parseDate(timezone);
    case Types.DATETIME:
    case Types.TIMESTAMP:
      if (helpers.typeMatch(type, dateStrings, Types)) {
        return packet.readLengthCodedString('ascii');
      }
      return packet.parseDateTime(timezone);
    case Types.TIME:
      return packet.readLengthCodedString('ascii');
    case Types.GEOMETRY:
      return packet.parseGeometryValue();
    case Types.VECTOR:
      return packet.parseVector();
    case Types.JSON:
      // Since for JSON columns mysql always returns charset 63 (BINARY),
      // we have to handle it according to JSON specs and use "utf8",
      // see https://github.com/sidorares/node-mysql2/issues/409
      return config.jsonStrings
        ? packet.readLengthCodedString('utf8')
        : packet.parseJson('utf8', supportBigNumbers);
    default:
      if (charset === Charsets.BINARY) {
        return packet.readLengthCodedBuffer();
      }
      return packet.readLengthCodedString(encoding);
  }
}

function createTypecastField(field, packet) {
  return {
    type: typeNames[field.columnType],
    extendedTypeName: field.extendedTypeName,
    extendedFormat: field.extendedFormat,
    length: field.columnLength,
    db: field.schema,
    table: field.table,
    name: field.name,
    string: function (encoding = field.encoding) {
      if (field.columnType === Types.JSON && encoding === field.encoding) {
        // Since for JSON columns mysql always returns charset 63 (BINARY),
        // we have to handle it according to JSON specs and use "utf8",
        // see https://github.com/sidorares/node-mysql2/issues/1661
        console.warn(
          `typeCast: JSON column "${field.name}" is interpreted as BINARY by default, recommended to manually set utf8 encoding: \`field.string("utf8")\``
        );
      }
      return packet.readLengthCodedString(encoding);
    },
    buffer: function () {
      return packet.readLengthCodedBuffer();
    },
    geometry: function () {
      return packet.parseGeometryValue();
    },
  };
}

function getTextParser(_fields, _options, config) {
  return {
    next(packet, fields, options) {
      const result = options.rowsAsArray ? [] : {};
      for (let i = 0; i < fields.length; i++) {
        const field = fields[i];
        const typeCast = options.typeCast ? options.typeCast : config.typeCast;
        const next = () =>
          readField({
            packet,
            field,
            type: field.columnType,
            encoding: field.encoding,
            charset: field.characterSet,
            config,
            options,
          });

        let value;

        if (options.typeCast === false) {
          value = packet.readLengthCodedBuffer();
        } else if (typeof typeCast === 'function') {
          value = typeCast(createTypecastField(field, packet), next);
        } else {
          value = next();
        }

        if (options.rowsAsArray) {
          result.push(value);
        } else if (typeof options.nestTables === 'string') {
          result[
            `${helpers.fieldEscape(field.table, false)}${options.nestTables}${helpers.fieldEscape(field.name, false)}`
          ] = value;
        } else if (options.nestTables) {
          const tableName = helpers.fieldEscape(field.table, false);
          if (!result[tableName]) {
            result[tableName] = {};
          }
          result[tableName][helpers.fieldEscape(field.name, false)] = value;
        } else {
          result[helpers.fieldEscape(field.name, false)] = value;
        }
      }

      return result;
    },
  };
}

module.exports = getTextParser;
