//  SPDX-License-Identifier: LGPL-2.1-or-later
//  Copyright (c) 2015-2024 MariaDB Corporation Ab

'use strict';

import * as FieldType from '../../const/field-type.js';
import * as Errors from '../../misc/errors.js';

// Maximum length (in characters) of a server-sent numeric string the driver will parse into a
// BigInt. Comfortably above any legitimate encoding (BIGINT is <= 20 digits), while small enough
// that the O(n^2) cost of accumulating the value stays negligible. Longer values are refused so an
// attacker-controlled text column can't be turned into a CPU-exhaustion vector.
const MAX_NUMERIC_STRING_LENGTH = 1024;

export function parser(col, opts) {
  // Fast path: For most types, we can directly return the default parser
  // This avoids the cost of the switch statement for common types
  const defaultParser = DEFAULT_PARSER_TYPE[col.columnType];
  if (defaultParser) return defaultParser;

  // Parser depending on column info
  switch (col.columnType) {
    case FieldType.DECIMAL:
    case FieldType.NEWDECIMAL:
      return col.scale === 0 ? readDecimalAsIntLengthCoded : readDecimalLengthCoded;

    case FieldType.BIGINT:
      if (opts.bigIntAsNumber || opts.supportBigNumbers) return readBigIntAsNumberLengthCoded;
      return readBigIntLengthCoded;

    case FieldType.GEOMETRY:
      const defaultVal = col.__getDefaultGeomVal();
      return function (packet, opts, throwUnexpectedError) {
        return packet.readGeometry(defaultVal);
      };

    case FieldType.BIT:
      if (col.columnLength === 1 && opts.bitOneIsBoolean) {
        return readBitAsBoolean;
      }
      return readBufferLengthEncoded;

    case FieldType.JSON:
      return opts.jsonStrings ? readStringLengthEncoded : readJson;

    default:
      if (col.isDataTypeFormatJson() && opts.autoJsonMap) {
        return readJson;
      }
      if (col.collation.index === 63) {
        return readBufferLengthEncoded;
      }
      if (col.isSet()) {
        return readSet;
      }
      return readStringLengthEncoded;
  }
}

export function castWrapper(column, packet, opts, nullBitmap, index) {
  const p = packet;

  column.string = () => p.readStringLengthEncoded();
  column.buffer = () => p.readBufferLengthEncoded();
  column.float = () => p.readFloatLengthCoded();
  column.tiny = column.short = column.int = () => p.readIntLengthEncoded();
  column.long = () => p.readBigIntLengthEncoded();
  column.decimal = () => p.readDecimalLengthEncoded();
  column.date = () => p.readDate(opts);
  column.datetime = () => p.readDateTime();

  // Only define geometry method if needed (likely less common)
  // Inline the geometry switch case for better performance
  column.geometry = () => {
    return p.readGeometry(column.__getDefaultGeomVal());
  };
}

const readIntLengthEncoded = (packet, opts, throwUnexpectedError) => packet.readIntLengthEncoded();
const readStringLengthEncoded = (packet, opts, throwUnexpectedError) => packet.readStringLengthEncoded();
const readFloatLengthCoded = (packet, opts, throwUnexpectedError) => packet.readFloatLengthCoded();
const readBigIntLengthCoded = (packet, opts, throwUnexpectedError) => packet.readBigIntLengthEncoded();
const readAsciiStringLengthEncoded = (packet, opts, throwUnexpectedError) => packet.readAsciiStringLengthEncoded();
const readBitAsBoolean = (packet, opts, throwUnexpectedError) => {
  const val = packet.readBufferLengthEncoded();
  return val == null ? null : val[0] === 1;
};
const readBufferLengthEncoded = (packet, opts, throwUnexpectedError) => packet.readBufferLengthEncoded();

const readJson = (packet, opts, throwUnexpectedError) => {
  const jsonStr = packet.readStringLengthEncoded();
  return jsonStr === null ? null : JSON.parse(jsonStr);
};

const readSet = (packet, opts, throwUnexpectedError) => {
  const string = packet.readStringLengthEncoded();
  return string == null ? null : string === '' ? [] : string.split(',');
};

const readDate = (packet, opts, throwUnexpectedError) =>
  opts.dateStrings ? packet.readAsciiStringLengthEncoded() : packet.readDate();

const readTimestamp = (packet, opts, throwUnexpectedError) =>
  opts.dateStrings ? packet.readAsciiStringLengthEncoded() : packet.readDateTime();

// Initialize the DEFAULT_PARSER_TYPE array with frequently used types
// Use a typed array for performance when accessing elements
const DEFAULT_PARSER_TYPE = new Array(256);
DEFAULT_PARSER_TYPE[FieldType.TINY] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.SHORT] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.INT] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.INT24] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.YEAR] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.FLOAT] = readFloatLengthCoded;
DEFAULT_PARSER_TYPE[FieldType.DOUBLE] = readFloatLengthCoded;
DEFAULT_PARSER_TYPE[FieldType.DATE] = readDate;
DEFAULT_PARSER_TYPE[FieldType.DATETIME] = readTimestamp;
DEFAULT_PARSER_TYPE[FieldType.TIMESTAMP] = readTimestamp;
DEFAULT_PARSER_TYPE[FieldType.TIME] = readAsciiStringLengthEncoded;

const readBigIntAsNumberLengthCoded = (packet, opts, throwUnexpectedError) => {
  const len = packet.readUnsignedLength();
  if (len === null) return null;

  // Fast path for small integers
  if (len < 16) {
    const val = packet._atoi(len);
    // We know we're here because either bigIntAsNumber or supportBigNumbers is true
    if (opts.supportBigNumbers && opts.bigNumberStrings) {
      return `${val}`;
    }
    return val;
  }

  if (len > MAX_NUMERIC_STRING_LENGTH) {
    packet.skip(len); // consume the oversized field so the protocol stream stays in sync
    return throwUnexpectedError(
      `numeric value length (${len}) exceeds maximum of ${MAX_NUMERIC_STRING_LENGTH} characters - refusing to parse`,
      false,
      null,
      '42000',
      Errors.client.ER_PARSING_PRECISION
    );
  }

  const val = packet.readBigIntFromLen(len);
  if (opts.bigIntAsNumber && opts.checkNumberRange && !Number.isSafeInteger(Number(val))) {
    return throwUnexpectedError(
      `value ${val} can't safely be converted to number`,
      false,
      null,
      '42000',
      Errors.client.ER_PARSING_PRECISION
    );
  }
  const numVal = Number(val);
  if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(numVal))) {
    return val.toString();
  }

  return numVal;
};

const readDecimalAsIntLengthCoded = (packet, opts, throwUnexpectedError) => {
  const valDec = packet.readDecimalLengthEncoded();
  if (valDec === null) return null;

  // Only perform conversions if needed based on options
  if (!(opts.decimalAsNumber || opts.supportBigNumbers)) return valDec;

  // Convert once
  const numValue = Number(valDec);

  // Check number range if required
  if (opts.decimalAsNumber && opts.checkNumberRange && !Number.isSafeInteger(numValue)) {
    return throwUnexpectedError(
      `value ${valDec} can't safely be converted to number`,
      false,
      null,
      '42000',
      Errors.client.ER_PARSING_PRECISION
    );
  }

  // Return string representation for big numbers if needed
  if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(numValue))) {
    return valDec;
  }

  return numValue;
};

const readDecimalLengthCoded = (packet, opts, throwUnexpectedError) => {
  const valDec = packet.readDecimalLengthEncoded();
  if (valDec === null) return null;

  // Only perform conversions if needed based on options
  if (!(opts.decimalAsNumber || opts.supportBigNumbers)) return valDec;

  const numberValue = Number(valDec);

  // Handle big numbers specifically
  if (
    opts.supportBigNumbers &&
    (opts.bigNumberStrings || (Number.isInteger(numberValue) && !Number.isSafeInteger(numberValue)))
  ) {
    return valDec;
  }

  return numberValue;
};
