import EventEmitter from 'eventemitter3';
import getPixels from 'get-pixels';
import iconv from 'iconv-lite';
import { MutableBuffer } from 'mutable-buffer';
import qr from 'qr-image';

const numToHexString = function(value) {
  value = +value;
  if (!isNaN(value)) {
    value = value.toString(16);
    while (value.length % 2 !== 0)
      value = `0${value}`;
  }
  return value;
};
const LF = "\n";
const FS = "";
const FF = "\f";
const GS = "";
const DLE = "";
const EOT = "";
const NUL = "\0";
const ESC = "\x1B";
const TAB = "t";
const EOL = "\n";
const FEED_CONTROL_SEQUENCES = {
  CTL_LF: "\n",
  CTL_GLF: "J\0",
  CTL_FF: "\f",
  CTL_CR: "\r",
  CTL_HT: "	",
  CTL_VT: "\v"
};
const CHARACTER_SPACING = {
  CS_DEFAULT: "\x1B \0",
  CS_SET: "\x1B "
};
const LINE_SPACING = {
  LS_DEFAULT: "\x1B2",
  LS_SET: "\x1B3"
};
const HARDWARE = {
  HW_INIT: "\x1B@",
  HW_SELECT: "\x1B=",
  HW_RESET: "\x1B?\n\0"
};
const CASH_DRAWER = {
  CD_KICK_2: "\x1Bp\0x",
  CD_KICK_5: "\x1Bpx"
};
const MARGINS = {
  BOTTOM: "\x1BO",
  LEFT: "\x1Bl",
  RIGHT: "\x1BQ"
};
const PAPER = {
  PAPER_FULL_CUT: "V\0",
  PAPER_PART_CUT: "V",
  PAPER_CUT_A: "VA",
  PAPER_CUT_B: "VB",
  STAR_FULL_CUT: "\x1Bd"
};
const TEXT_FORMAT = {
  TXT_NORMAL: "\x1B!\0",
  TXT_2HEIGHT: "\x1B!",
  TXT_2WIDTH: "\x1B! ",
  TXT_4SQUARE: "\x1B!0",
  STAR_TXT_EMPHASIZED: "\x1BE",
  STAR_CANCEL_TXT_EMPHASIZED: "\x1BF",
  TXT_CUSTOM_SIZE(width, height) {
    width = width > 8 ? 8 : width;
    width = width < 1 ? 1 : width;
    height = height > 8 ? 8 : height;
    height = height < 1 ? 1 : height;
    const widthDec = (width - 1) * 16;
    const heightDec = height - 1;
    const sizeDec = widthDec + heightDec;
    return `!${String.fromCharCode(sizeDec)}`;
  },
  TXT_HEIGHT: {
    1: "\0",
    2: "",
    3: "",
    4: "",
    5: "",
    6: "",
    7: "",
    8: "\x07"
  },
  TXT_WIDTH: {
    1: "\0",
    2: "",
    3: " ",
    4: "0",
    5: "@",
    6: "P",
    7: "`",
    8: "p"
  },
  TXT_UNDERL_OFF: "\x1B-\0",
  TXT_UNDERL_ON: "\x1B-",
  TXT_UNDERL2_ON: "\x1B-",
  TXT_BOLD_OFF: "\x1BE\0",
  TXT_BOLD_ON: "\x1BE",
  TXT_ITALIC_OFF: "\x1B4\0",
  TXT_ITALIC_ON: "\x1B4",
  TXT_FONT_A: "\x1BM\0",
  TXT_FONT_B: "\x1BM",
  TXT_FONT_C: "\x1BM",
  TXT_ALIGN_LT: "\x1Ba\0",
  TXT_ALIGN_CT: "\x1Ba",
  TXT_ALIGN_RT: "\x1Ba",
  STAR_TXT_ALIGN_LA: "\x1Ba\0",
  STAR_TXT_ALIGN_CA: "\x1Ba",
  STAR_TXT_ALIGN_RA: "\x1Ba"
};
const MODEL = {
  QSPRINTER: {
    BARCODE_MODE: {
      ON: "EC",
      OFF: "EC\0"
    },
    BARCODE_HEIGHT_DEFAULT: "h\xA2",
    CODE2D_FORMAT: {
      PIXEL_SIZE: {
        CMD: "\x1B##QPIX",
        MIN: 1,
        MAX: 24,
        DEFAULT: 12
      },
      VERSION: {
        CMD: "(k\x001C",
        MIN: 1,
        MAX: 16,
        DEFAULT: 3
      },
      LEVEL: {
        CMD: "(k\x001E",
        OPTIONS: {
          L: 48,
          M: 49,
          Q: 50,
          H: 51
        }
      },
      LEN_OFFSET: 3,
      SAVEBUF: {
        CMD_P1: "(k",
        CMD_P2: "1P0"
      },
      PRINTBUF: {
        CMD_P1: "(k",
        CMD_P2: "1Q0"
      }
    }
  }
};
const BARCODE_FORMAT = {
  BARCODE_TXT_OFF: "H\0",
  BARCODE_TXT_ABV: "H",
  BARCODE_TXT_BLW: "H",
  BARCODE_TXT_BTH: "H",
  BARCODE_FONT_A: "f\0",
  BARCODE_FONT_B: "f",
  BARCODE_HEIGHT(height) {
    return Buffer.from(`1d68${numToHexString(height)}`, "hex");
  },
  BARCODE_WIDTH: {
    1: "w",
    2: "w",
    3: "w",
    4: "w",
    5: "w"
  },
  BARCODE_HEIGHT_DEFAULT: "hd",
  BARCODE_WIDTH_DEFAULT: "w",
  BARCODE_UPC_A: "k\0",
  BARCODE_UPC_E: "k",
  BARCODE_EAN13: "k",
  BARCODE_EAN8: "k",
  BARCODE_CODE39: "k",
  BARCODE_ITF: "k",
  BARCODE_NW7: "k",
  BARCODE_CODE93: "kH",
  BARCODE_CODE128: "kI"
};
const CODE2D_FORMAT = {
  TYPE_PDF417: `${GS}Z\0`,
  TYPE_DATAMATRIX: `${GS}Z`,
  TYPE_QR: `${GS}Z`,
  CODE2D: `${ESC}Z`,
  QR_LEVEL_L: "L",
  QR_LEVEL_M: "M",
  QR_LEVEL_Q: "Q",
  QR_LEVEL_H: "H"
};
const IMAGE_FORMAT = {
  S_RASTER_N: "v0\0",
  S_RASTER_2W: "v0",
  S_RASTER_2H: "v0",
  S_RASTER_Q: "v0"
};
const BITMAP_FORMAT = {
  BITMAP_S8: "\x1B*\0",
  BITMAP_D8: "\x1B*",
  BITMAP_S24: "\x1B* ",
  BITMAP_D24: "\x1B*!"
};
const GSV0_FORMAT = {
  GSV0_NORMAL: "v0\0",
  GSV0_DW: "v0",
  GSV0_DH: "v0",
  GSV0_DWDH: "v0"
};
const BEEP = "\x1BB";
const COLOR = {
  0: "\x1Br\0",
  1: "\x1Br",
  REVERSE: "B1",
  UNREVERSE: "B0"
};
const RECEIPT_ENHANCEMENT = "(E";
const CHARACTER_SET = {
  TM_T20: {
    US: 0,
    FR: 1,
    DE: 2,
    EN: 3,
    DK: 4,
    SE: 5,
    IT: 6,
    ES: 7,
    JP: 8,
    NO: 9,
    DK_2: 10,
    ES_2: 11,
    LATIN_A: 12,
    KR: 13,
    SI: 14,
    HR: 14,
    CN: 15,
    VN: 16,
    ARABIA: 17
  }
};
const CHARACTER_CODE_TABLE = {
  TM_T20: {
    PC437: 0,
    KATAKANA: 1,
    PC850: 2,
    PC860: 3,
    PC863: 4,
    PC865: 5,
    PC851: 11,
    PC853: 12,
    PC857: 13,
    PC737: 14,
    ISO8859_7: 15,
    WPC1252: 16,
    PC866: 17,
    PC852: 18,
    PC858: 19,
    KU42: 20,
    TIS11: 21,
    TIS18: 26,
    TCVN_3: 30,
    TCVN_3_2: 31,
    PC720: 32,
    WPC775: 33,
    PC855: 34,
    PC861: 35,
    PC862: 36,
    PC864: 37,
    PC869: 38,
    ISO8859_2: 39,
    ISO8859_15: 40,
    PC1098: 41,
    PC1118: 42,
    PC1119: 43,
    PC1125: 44,
    WPC1250: 45,
    WPC1251: 46,
    WPC1253: 47,
    WPC1254: 48,
    WPC1255: 49,
    WPC1256: 50,
    WPC1257: 51,
    WPC1258: 52,
    KZ_1048: 53,
    USER_PAGE2: 254,
    USER_PAGE1: 255
  }
};

const _ = {
  __proto__: null,
  LF: LF,
  FS: FS,
  FF: FF,
  GS: GS,
  DLE: DLE,
  EOT: EOT,
  NUL: NUL,
  ESC: ESC,
  TAB: TAB,
  EOL: EOL,
  FEED_CONTROL_SEQUENCES: FEED_CONTROL_SEQUENCES,
  CHARACTER_SPACING: CHARACTER_SPACING,
  LINE_SPACING: LINE_SPACING,
  HARDWARE: HARDWARE,
  CASH_DRAWER: CASH_DRAWER,
  MARGINS: MARGINS,
  PAPER: PAPER,
  TEXT_FORMAT: TEXT_FORMAT,
  MODEL: MODEL,
  BARCODE_FORMAT: BARCODE_FORMAT,
  CODE2D_FORMAT: CODE2D_FORMAT,
  IMAGE_FORMAT: IMAGE_FORMAT,
  BITMAP_FORMAT: BITMAP_FORMAT,
  GSV0_FORMAT: GSV0_FORMAT,
  BEEP: BEEP,
  COLOR: COLOR,
  RECEIPT_ENHANCEMENT: RECEIPT_ENHANCEMENT,
  CHARACTER_SET: CHARACTER_SET,
  CHARACTER_CODE_TABLE: CHARACTER_CODE_TABLE
};

class Image {
  constructor(pixels) {
    this.data = [];
    this.pixels = pixels;
    const rgbaData = [];
    for (let i = 0; i < this.pixels.data.length; i += this.size.colors) {
      rgbaData.push(
        new Array(this.size.colors).fill(0).map((_, b) => this.pixels.data[i + b])
      );
    }
    this.data = rgbaData.map(([r, g, b, a]) => this.RGB2Gray(r, g, b, a));
  }
  get size() {
    return {
      width: this.pixels.shape[0],
      height: this.pixels.shape[1],
      colors: this.pixels.shape[2]
    };
  }
  RGB2Gray(r, g, b, a) {
    if (a === 0) {
      return false;
    }
    return 0.299 * r + 0.578 * g + 0.114 * b < 128;
  }
  toBitmap(density = 24) {
    const result = [];
    let x, y, b, l, i;
    const c = density / 8;
    const n = Math.ceil(this.size.height / density);
    for (y = 0; y < n; y++) {
      const ld = result[y] = [];
      for (x = 0; x < this.size.width; x++) {
        for (b = 0; b < density; b++) {
          i = x * c + (b >> 3);
          if (ld[i] === void 0) {
            ld[i] = 0;
          }
          l = y * density + b;
          if (l < this.size.height) {
            if (this.data[l * this.size.width + x]) {
              ld[i] += 128 >> (b & 7);
            }
          }
        }
      }
    }
    return {
      data: result,
      density
    };
  }
  toRaster() {
    const result = [];
    const { width, height } = this.size;
    const n = Math.ceil(width / 8);
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < n; x++) {
        for (let b = 0; b < 8; b++) {
          const i = x * 8 + b;
          if (result[y * n + x] === void 0) {
            result[y * n + x] = 0;
          }
          const c = x * 8 + b;
          if (c < width) {
            if (this.data[y * width + i]) {
              result[y * n + x] += 128 >> (b & 7);
            }
          }
        }
      }
    }
    return {
      data: result,
      width: n,
      height
    };
  }
  static load(url, type = null) {
    return new Promise((resolve, reject) => {
      getPixels(url, type ?? "", (error, pixels) => {
        if (error) {
          reject(error);
        } else {
          resolve(new Image(pixels));
        }
      });
    });
  }
}

class DeviceStatus {
  constructor(byte) {
    this.bits = [];
    this.bitsAsc = [];
    this.byte = byte;
    for (let j = 7; j >= 0; j--) {
      const bit = byte & 1 << j ? 1 : 0;
      this.bits.push(bit);
    }
    this.bitsAsc = this.bits.slice();
    this.bitsAsc.reverse();
  }
  getBits() {
    return this.bits.join("");
  }
  toBaseJSON(name) {
    return {
      className: name,
      byte: this.byte,
      bits: this.getBits(),
      statuses: []
    };
  }
}
class PrinterStatus extends DeviceStatus {
  static commands() {
    return [DLE, EOT, String.fromCharCode(1)];
  }
  toJSON() {
    const result = super.toBaseJSON("PrinterStatus");
    for (let i = 0; i < 8; i++) {
      let label = "";
      let status = "ok" /* Ok */;
      switch (i) {
        case 2:
          if (this.bitsAsc[i] === 1)
            label = "Drawer kick-out connector pin 3 is HIGH";
          else
            label = "Drawer kick-out connector pin 3 is LOW";
          break;
        case 3:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Offline";
          } else {
            label = "Online";
          }
          break;
        case 5:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Waiting for online recovery";
          } else {
            label = "Not waiting for online recovery";
          }
          break;
        case 6:
          if (this.bitsAsc[i] === 1)
            label = "Paper feed button is being pressed";
          else
            label = "Paper feed button is not being pressed";
          break;
        default:
          label = "Fixed";
          break;
      }
      result.statuses.push({
        bit: i,
        value: this.bitsAsc[i],
        label,
        status
      });
    }
    return result;
  }
}
class OfflineCauseStatus extends DeviceStatus {
  static commands() {
    return [DLE, EOT, String.fromCharCode(2)];
  }
  toJSON() {
    const result = super.toBaseJSON("OfflineCauseStatus");
    for (let i = 0; i < 8; i++) {
      let label = "";
      let status = "ok" /* Ok */;
      switch (i) {
        case 2:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Cover is open";
          } else {
            label = "Cover is closed";
          }
          break;
        case 3:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Paper is being fed by the paper feed button";
          } else {
            label = "Paper is not being fed by the paper feed button";
          }
          break;
        case 5:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Printing stops due to a paper-end";
          } else {
            label = "No paper-end stop";
          }
          break;
        case 6:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Error occurred";
          } else {
            label = "No error";
          }
          break;
        default:
          label = "Fixed";
          break;
      }
      result.statuses.push({
        bit: i,
        value: this.bitsAsc[i],
        label,
        status
      });
    }
    return result;
  }
}
class ErrorCauseStatus extends DeviceStatus {
  static commands() {
    return [DLE, EOT, String.fromCharCode(3)];
  }
  toJSON() {
    const result = super.toBaseJSON("ErrorCauseStatus");
    for (let i = 0; i < 8; i++) {
      let label = "";
      let status = "ok" /* Ok */;
      switch (i) {
        case 2:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Recoverable error occurred";
          } else {
            label = "No recoverable error";
          }
          break;
        case 3:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Autocutter error occurred";
          } else {
            label = "No autocutter error";
          }
          break;
        case 5:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Unrecoverable error occurred";
          } else {
            label = "No unrecoverable error";
          }
          break;
        case 6:
          if (this.bitsAsc[i] === 1) {
            status = "error" /* Error */;
            label = "Auto-recoverable error occurred";
          } else {
            label = "No auto-recoverable error";
          }
          break;
        default:
          label = "Fixed";
          break;
      }
      result.statuses.push({
        bit: i,
        value: this.bitsAsc[i],
        label,
        status
      });
    }
    return result;
  }
}
class RollPaperSensorStatus extends DeviceStatus {
  static commands() {
    return [DLE, EOT, String.fromCharCode(4)];
  }
  toJSON() {
    const result = super.toBaseJSON("RollPaperSensorStatus");
    for (let i = 0; i <= 1; i++) {
      result.statuses.push({
        bit: i,
        value: this.bitsAsc[i],
        label: "Fixed",
        status: "ok" /* Ok */
      });
    }
    let label = "";
    let status = "ok" /* Ok */;
    if (this.bitsAsc[2] === 1 && this.bitsAsc[3] === 1) {
      status = "warning" /* Warning */;
      label = "Roll paper near-end sensor: paper near-end";
    } else if (this.bitsAsc[2] === 0 && this.bitsAsc[3] === 0) {
      label = "Roll paper near-end sensor: paper adequate";
    }
    result.statuses.push({
      bit: "2,3",
      value: `${this.bitsAsc[2]}${this.bitsAsc[3]}`,
      label,
      status
    });
    result.statuses.push({
      bit: 4,
      value: this.bitsAsc[4],
      label: "Fixed",
      status: "ok" /* Ok */
    });
    label = "";
    status = "ok" /* Ok */;
    if (this.bitsAsc[5] === 1 && this.bitsAsc[6] === 1) {
      status = "error" /* Error */;
      label = "Roll paper end sensor: paper not present";
    } else if (this.bitsAsc[5] === 0 && this.bitsAsc[6] === 0) {
      label = "Roll paper end sensor: paper present";
    }
    result.statuses.push({
      bit: "5,6",
      value: `${this.bitsAsc[5]}${this.bitsAsc[6]}`,
      label,
      status
    });
    for (let i = 7; i <= 8; i++) {
      result.statuses.push({
        bit: i,
        value: this.bitsAsc[i],
        label: "Fixed",
        status: "ok" /* Ok */
      });
    }
    return result;
  }
}

function getParityBit(str) {
  let parity = 0;
  const reversedCode = str.split("").reverse().join("");
  for (let counter = 0; counter < reversedCode.length; counter += 1)
    parity += parseInt(reversedCode.charAt(counter), 10) * 3 ** ((counter + 1) % 2);
  return ((10 - parity % 10) % 10).toString();
}
function codeLength(str) {
  const buff = Buffer.from(str.length.toString(16), "hex");
  return buff.toString();
}
function charLength(char) {
  const code = char.charCodeAt(0);
  return code > 127 && code <= 65535 ? 2 : 1;
}
function textLength(str) {
  return str.split("").reduce((accLen, char) => {
    return accLen + charLength(char);
  }, 0);
}
function textSubstring(str, start, end) {
  let accLen = 0;
  return str.split("").reduce((accStr, char) => {
    accLen = accLen + charLength(char);
    return accStr + (accLen > start && (!end || accLen <= end) ? char : "");
  }, "");
}
function upperCase(string) {
  return string.toUpperCase();
}
function isKey(key, of) {
  return key in of;
}
function intLowHighHex(input, length = 1) {
  if (input < 0 || input % 1 !== 0) {
    throw new Error("Input must be greater or equal than 0");
  } else if (length < 0) {
    throw new Error("Length muste be greater than 0");
  }
  let ret = "";
  for (let i = 0; i < length; i++) {
    let value = (input % 256).toString(16).toUpperCase();
    if (value === "0" || value.length % 2 !== 0) {
      value = `0${value}`;
    }
    ret += value;
    input = Math.floor(input / 256);
  }
  return ret;
}
const SPLIT_REGEX = /(^(?:\d\d){2,})|((?<=\D)(?:(?:\d\d){3,})(?=\d?\D))|((?:\d\d){2,}$)/;
function splitForCode128(str) {
  if (str.length <= 4)
    return [str];
  return str.split(SPLIT_REGEX).filter((s) => s !== "" && s !== void 0);
}
const USE_CODEC_REGEX = /^((?:\d\d){1,})$/;
function genCode128forXprinter(barcode) {
  const toCodeC = (s) => "{C" + s.match(/\d{2}/g)?.map((num) => String.fromCharCode(Number(num)))?.join("");
  const toCodeB = (s) => "{B" + s.replace("{", "{{");
  const blocks = splitForCode128(barcode);
  const dataPart = blocks.map((block) => USE_CODEC_REGEX.test(block) ? toCodeC(block) : toCodeB(block)).join("");
  const dataLength = dataPart.length;
  if (dataLength > 255)
    throw new Error("Barcode data is too long");
  return String.fromCharCode(dataLength) + dataPart;
}

class Printer extends EventEmitter {
  constructor(adapter, options) {
    super();
    this.buffer = new MutableBuffer();
    this._model = null;
    this.adapter = adapter;
    this.options = options;
    this.encoding = options.encoding ?? "GB18030";
    this.width = options.width ?? 48;
  }
  model(model) {
    this._model = model;
    return this;
  }
  setCharacterCodeTable(codeTable) {
    this.buffer.write(ESC);
    this.buffer.write(TAB);
    this.buffer.writeUInt8(codeTable);
    return this;
  }
  setCharset(charset = CHARACTER_SET.TM_T20.US) {
    this.buffer.write(ESC);
    this.buffer.write("R");
    this.buffer.writeUInt8(charset);
    return this;
  }
  marginBottom(size) {
    this.buffer.write(MARGINS.BOTTOM);
    this.buffer.writeUInt8(size);
    return this;
  }
  marginLeft(size) {
    this.buffer.write(MARGINS.LEFT);
    this.buffer.writeUInt8(size);
    return this;
  }
  setMarginLeft(size) {
    if (size > 65535) {
      throw new Error("Max margin range exceeded");
    }
    this.buffer.write(GS);
    this.buffer.write("L");
    const nL_nH = intLowHighHex(size, 2);
    this.buffer.write(Buffer.from(nL_nH, "hex"));
    return this;
  }
  marginRight(size) {
    this.buffer.write(MARGINS.RIGHT);
    this.buffer.writeUInt8(size);
    return this;
  }
  print(content) {
    this.buffer.write(content);
    return this;
  }
  println(content) {
    return this.print(content + EOL);
  }
  newLine(count = 1) {
    if (count <= 0)
      throw Error("Count cannot be less or equal than 0");
    else
      return this.print(EOL.repeat(count));
  }
  text(content, encoding = this.encoding) {
    return this.print(iconv.encode(`${content}${EOL}`, encoding));
  }
  drawLine(character = "-") {
    let buffer;
    if (Buffer.isBuffer(character)) {
      buffer = character;
    } else {
      buffer = Buffer.from(character);
    }
    for (let i = 0; i < this.width; i++)
      this.buffer.write(buffer);
    this.newLine();
    return this;
  }
  table(data, encoding = this.encoding) {
    const cellWidth = this.width / data.length;
    let lineTxt = "";
    for (let i = 0; i < data.length; i++) {
      lineTxt += data[i].toString();
      const spaces = cellWidth - data[i].toString().length;
      for (let j = 0; j < spaces; j++)
        lineTxt += " ";
    }
    this.buffer.write(iconv.encode(lineTxt + EOL, encoding));
    return this;
  }
  tableCustom(data, options = { size: [1, 1], encoding: this.encoding }) {
    const [width, height] = options.size;
    const baseWidth = Math.floor(this.width / width);
    let cellWidth = Math.floor(baseWidth / data.length);
    let leftoverSpace = baseWidth - cellWidth * data.length;
    let lineStr = "";
    let secondLineEnabled = false;
    const secondLine = [];
    for (let i = 0; i < data.length; i++) {
      const obj = data[i];
      const align = upperCase(obj.align || "left");
      const textLength$1 = textLength(obj.text);
      if ("width" in obj) {
        cellWidth = baseWidth * obj.width;
      } else if (obj.cols) {
        cellWidth = obj.cols / width;
        leftoverSpace = 0;
      }
      let originalText = null;
      if (cellWidth < textLength$1) {
        originalText = obj.text;
        obj.text = textSubstring(obj.text, 0, cellWidth);
      }
      if (align === "CENTER") {
        const spaces = (cellWidth - textLength$1) / 2;
        for (let s = 0; s < spaces; s++)
          lineStr += " ";
        if (obj.text !== "") {
          if (obj.style)
            lineStr += `${this._getStyle(obj.style)}${obj.text}${this._getStyle("NORMAL")}`;
          else
            lineStr += obj.text;
        }
        for (let s = 0; s < spaces - 1; s++)
          lineStr += " ";
      } else if (align === "RIGHT") {
        let spaces = cellWidth - textLength$1;
        if (leftoverSpace > 0) {
          spaces += leftoverSpace;
          leftoverSpace = 0;
        }
        for (let s = 0; s < spaces; s++)
          lineStr += " ";
        if (obj.text !== "") {
          if (obj.style)
            lineStr += `${this._getStyle(obj.style)}${obj.text}${this._getStyle("NORMAL")}`;
          else
            lineStr += obj.text;
        }
      } else {
        if (obj.text !== "") {
          if (obj.style)
            lineStr += `${this._getStyle(obj.style)}${obj.text}${this._getStyle("NORMAL")}`;
          else
            lineStr += obj.text;
        }
        let spaces = Math.floor(cellWidth - textLength$1);
        if (leftoverSpace > 0) {
          spaces += leftoverSpace;
          leftoverSpace = 0;
        }
        for (let s = 0; s < spaces; s++)
          lineStr += " ";
      }
      if (originalText !== null) {
        secondLineEnabled = true;
        obj.text = textSubstring(originalText, cellWidth);
        secondLine.push(obj);
      } else {
        obj.text = "";
        secondLine.push(obj);
      }
    }
    if (width > 1 || height > 1) {
      lineStr = TEXT_FORMAT.TXT_CUSTOM_SIZE(width, height) + lineStr + TEXT_FORMAT.TXT_NORMAL;
    }
    this.buffer.write(
      iconv.encode(lineStr + EOL, options.encoding || this.encoding)
    );
    if (secondLineEnabled) {
      return this.tableCustom(secondLine, options);
    } else {
      return this;
    }
  }
  pureText(content, encoding = this.encoding) {
    return this.print(iconv.encode(content, encoding));
  }
  encode(encoding) {
    this.encoding = encoding;
    return this;
  }
  feed(n = 1) {
    this.buffer.write(new Array(n).fill(EOL).join(""));
    return this;
  }
  control(ctrl) {
    this.buffer.write(FEED_CONTROL_SEQUENCES[`CTL_${upperCase(ctrl)}`]);
    return this;
  }
  align(align) {
    this.buffer.write(TEXT_FORMAT[`TXT_ALIGN_${upperCase(align)}`]);
    return this;
  }
  font(family) {
    this.buffer.write(TEXT_FORMAT[`TXT_FONT_${upperCase(family)}`]);
    if (family.toUpperCase() === "A")
      this.width = this.options?.width || 42;
    else
      this.width = this.options?.width || 56;
    return this;
  }
  _getStyle(boldOrString, italic, underline) {
    if (typeof boldOrString === "string") {
      switch (upperCase(boldOrString)) {
        case "B":
          return this._getStyle(true, false, 0);
        case "I":
          return this._getStyle(false, true, 0);
        case "U":
          return this._getStyle(false, false, 1);
        case "U2":
          return this._getStyle(false, false, 2);
        case "BI":
          return this._getStyle(true, true, 0);
        case "BIU":
          return this._getStyle(true, true, 1);
        case "BIU2":
          return this._getStyle(true, true, 2);
        case "BU":
          return this._getStyle(true, false, 1);
        case "BU2":
          return this._getStyle(true, false, 2);
        case "IU":
          return this._getStyle(false, true, 1);
        case "IU2":
          return this._getStyle(false, true, 2);
        case "NORMAL":
        default:
          return this._getStyle(false, false, 0);
      }
    } else {
      let styled = `${boldOrString ? TEXT_FORMAT.TXT_BOLD_ON : TEXT_FORMAT.TXT_BOLD_OFF}${italic ? TEXT_FORMAT.TXT_ITALIC_ON : TEXT_FORMAT.TXT_ITALIC_OFF}`;
      if (underline === 0 || underline === false)
        styled += TEXT_FORMAT.TXT_UNDERL_OFF;
      else if (underline === 1 || underline === true)
        styled += TEXT_FORMAT.TXT_UNDERL_ON;
      else if (underline === 2)
        styled += TEXT_FORMAT.TXT_UNDERL2_ON;
      return styled;
    }
  }
  style(boldOrString, italic, underline) {
    const style = typeof boldOrString === "string" ? this._getStyle(boldOrString) : this._getStyle(boldOrString, italic, underline);
    this.buffer.write(style);
    return this;
  }
  size(width, height) {
    this.buffer.write(TEXT_FORMAT.TXT_CUSTOM_SIZE(width, height));
    return this;
  }
  spacing(n) {
    if (n === void 0 || n === null) {
      this.buffer.write(CHARACTER_SPACING.CS_DEFAULT);
    } else {
      this.buffer.write(CHARACTER_SPACING.CS_SET);
      this.buffer.writeUInt8(n);
    }
    return this;
  }
  lineSpace(n) {
    if (n === void 0 || n === null) {
      this.buffer.write(LINE_SPACING.LS_DEFAULT);
    } else {
      this.buffer.write(LINE_SPACING.LS_SET);
      this.buffer.writeUInt8(n);
    }
    return this;
  }
  hardware(hw) {
    this.buffer.write(HARDWARE[`HW_${upperCase(hw)}`]);
    return this;
  }
  barcode(code, type, options) {
    options.font = options.font ?? "a";
    options.position = options.position ?? "blw";
    options.includeParity = options.includeParity ?? true;
    const convertCode = typeof code === "number" ? code.toString(10) : code;
    let parityBit = "";
    let codeLength$1 = "";
    if (typeof type === "undefined" || type === null)
      throw new TypeError("barcode type is required");
    if (type === "EAN13" && convertCode.length !== 12)
      throw new Error("EAN13 Barcode type requires code length 12");
    if (type === "EAN8" && convertCode.length !== 7)
      throw new Error("EAN8 Barcode type requires code length 7");
    if (["UPC_A", "UPC-A", "UPC-E", "UPC_E", "EAN13", "EAN8", "ITF", "NW7"].includes(type) && !/^\d+$/.test(convertCode))
      throw new Error(type + " Barcode type only support numbers");
    if (this._model === "qsprinter")
      this.buffer.write(MODEL.QSPRINTER.BARCODE_MODE.ON);
    if (this._model === "qsprinter") ; else if (isKey(options.width, BARCODE_FORMAT.BARCODE_WIDTH)) {
      this.buffer.write(BARCODE_FORMAT.BARCODE_WIDTH[options.width]);
    } else {
      this.buffer.write(BARCODE_FORMAT.BARCODE_WIDTH_DEFAULT);
    }
    if (options.height >= 1 && options.height <= 255) {
      this.buffer.write(BARCODE_FORMAT.BARCODE_HEIGHT(options.height));
    } else {
      if (this._model === "qsprinter")
        this.buffer.write(MODEL.QSPRINTER.BARCODE_HEIGHT_DEFAULT);
      else
        this.buffer.write(BARCODE_FORMAT.BARCODE_HEIGHT_DEFAULT);
    }
    if (this._model === "qsprinter") ; else {
      this.buffer.write(BARCODE_FORMAT[`BARCODE_FONT_${upperCase(options.font)}`]);
    }
    this.buffer.write(BARCODE_FORMAT[`BARCODE_TXT_${upperCase(options.position)}`]);
    let normalizedType = upperCase(type);
    if (normalizedType === "UPC-A")
      normalizedType = "UPC_A";
    else if (normalizedType === "UPC-E")
      normalizedType = "UPC_E";
    this.buffer.write(BARCODE_FORMAT[`BARCODE_${normalizedType}`]);
    if (options.includeParity) {
      if (type === "EAN13" || type === "EAN8")
        parityBit = getParityBit(convertCode);
    }
    if (type === "CODE128" || type === "CODE93")
      codeLength$1 = codeLength(convertCode);
    if (this._model === "xprinter" && type === "CODE128") {
      const code128Data = genCode128forXprinter(convertCode);
      this.buffer.write(code128Data);
    } else {
      this.buffer.write(`${codeLength$1 + convertCode + (options.includeParity ? parityBit : "")}\0`);
    }
    if (this._model === "qsprinter")
      this.buffer.write(MODEL.QSPRINTER.BARCODE_MODE.OFF);
    return this;
  }
  qrcode(content, version, level, size) {
    if (this._model !== "qsprinter") {
      this.buffer.write(CODE2D_FORMAT.TYPE_QR);
      this.buffer.write(CODE2D_FORMAT.CODE2D);
      this.buffer.writeUInt8(version ?? 3);
      this.buffer.write(CODE2D_FORMAT[`QR_LEVEL_${upperCase(level ?? "L")}`]);
      this.buffer.writeUInt8(size ?? 6);
      this.buffer.writeUInt16LE(content.length);
      this.buffer.write(content);
    } else {
      const dataRaw = iconv.encode(content, "utf8");
      if (dataRaw.length < 1 && dataRaw.length > 2710)
        throw new Error("Invalid code length in byte. Must be between 1 and 2710");
      if (!size || size && typeof size !== "number")
        size = MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.DEFAULT;
      else if (size && size < MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.MIN)
        size = MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.MIN;
      else if (size && size > MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.MAX)
        size = MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.MAX;
      this.buffer.write(MODEL.QSPRINTER.CODE2D_FORMAT.PIXEL_SIZE.CMD);
      this.buffer.writeUInt8(size);
      if (!version || version && typeof version !== "number")
        version = MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.DEFAULT;
      else if (version && version < MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.MIN)
        version = MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.MIN;
      else if (version && version > MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.MAX)
        version = MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.MAX;
      this.buffer.write(MODEL.QSPRINTER.CODE2D_FORMAT.VERSION.CMD);
      this.buffer.writeUInt8(version);
      this.buffer.write(MODEL.QSPRINTER.CODE2D_FORMAT.LEVEL.CMD);
      this.buffer.write(MODEL.QSPRINTER.CODE2D_FORMAT.LEVEL.OPTIONS[upperCase(level ?? "L")]);
      this.buffer.write(MODEL.QSPRINTER.CODE2D_FORMAT.SAVEBUF.CMD_P1);
      this.buffer.writeUInt16LE(dataRaw.length + MODEL.QSPRINTER.CODE2D_FORMAT.LEN_OFFSET);
      this.buffer.write(MODEL.QSPRINTER.CODE2D_FORMAT.SAVEBUF.CMD_P2);
      this.buffer.write(dataRaw);
      this.buffer.write(MODEL.QSPRINTER.CODE2D_FORMAT.PRINTBUF.CMD_P1);
      this.buffer.writeUInt16LE(dataRaw.length + MODEL.QSPRINTER.CODE2D_FORMAT.LEN_OFFSET);
      this.buffer.write(MODEL.QSPRINTER.CODE2D_FORMAT.PRINTBUF.CMD_P2);
    }
    return this;
  }
  qrimage(text, options = { type: "png", mode: "dhdw" }) {
    return new Promise((resolve, reject) => {
      const buffer = qr.imageSync(text, options);
      const type = ["image", options.type].join("/");
      getPixels(buffer, type, (err, pixels) => {
        if (err)
          reject(err);
        this.raster(new Image(pixels), options.mode);
        resolve(this);
      });
    });
  }
  async image(image, density = "d24") {
    if (!(image instanceof Image))
      throw new TypeError("Only escpos.Image supported");
    const n = ~["D8", "S8"].indexOf(upperCase(density)) ? 1 : 3;
    const header = BITMAP_FORMAT[`BITMAP_${upperCase(density)}`];
    const bitmap = image.toBitmap(n * 8);
    this.lineSpace(0);
    bitmap.data.forEach((line) => {
      this.buffer.write(header);
      this.buffer.writeUInt16LE(line.length / n);
      this.buffer.write(line);
      this.buffer.write(EOL);
    });
    await new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 200);
    });
    return this.lineSpace();
  }
  raster(image, mode = "NORMAL") {
    if (!(image instanceof Image))
      throw new TypeError("Only escpos.Image supported");
    mode = upperCase(mode);
    if (mode === "DHDW" || mode === "DWH" || mode === "DHW")
      mode = "DWDH";
    const raster = image.toRaster();
    const header = GSV0_FORMAT[`GSV0_${mode}`];
    this.buffer.write(header);
    this.buffer.writeUInt16LE(raster.width);
    this.buffer.writeUInt16LE(raster.height);
    this.buffer.write(raster.data);
    return this;
  }
  cashdraw(pin = 2) {
    this.buffer.write(CASH_DRAWER[pin === 5 ? "CD_KICK_5" : "CD_KICK_2"]);
    return this;
  }
  beep(n, t) {
    this.buffer.write(BEEP);
    this.buffer.writeUInt8(n);
    this.buffer.writeUInt8(t);
    return this;
  }
  flush() {
    return new Promise((resolve, reject) => {
      const buf = this.buffer.flush();
      this.adapter.write(buf, (error) => {
        if (error)
          reject(error);
        else
          resolve(this);
      });
    });
  }
  cut(partial = false, feed = 3) {
    this.feed(feed);
    this.buffer.write(PAPER[partial ? "PAPER_PART_CUT" : "PAPER_FULL_CUT"]);
    return this;
  }
  async close(...closeArgs) {
    await this.flush();
    return new Promise((resolve, reject) => {
      this.adapter.close((error) => {
        if (error)
          reject(error);
        resolve(this);
      }, ...closeArgs);
    });
  }
  color(color) {
    if (color !== 0 && color !== 1) {
      console.warn(`Unknown color ${color}`);
      this.buffer.write(COLOR[0]);
    } else {
      this.buffer.write(COLOR[color]);
    }
    return this;
  }
  setReverseColors(reverse) {
    this.buffer.write(reverse ? COLOR.REVERSE : COLOR.UNREVERSE);
    return this;
  }
  raw(data) {
    if (Buffer.isBuffer(data)) {
      this.buffer.write(data);
    } else if (typeof data === "string") {
      data = data.toLowerCase();
      this.buffer.write(Buffer.from(data.replace(/(\s|:)/g, ""), "hex"));
    }
    return this;
  }
  getStatus(StatusClass) {
    return new Promise((resolve) => {
      this.adapter.read((data) => {
        const byte = data.readInt8(0);
        resolve(new StatusClass(byte));
      });
      StatusClass.commands().forEach((c) => {
        this.buffer.write(c);
      });
    });
  }
  getStatuses() {
    return new Promise((resolve, reject) => {
      this.adapter.read((data) => {
        const buffer = [];
        for (let i = 0; i < data.byteLength; i++)
          buffer.push(data.readInt8(i));
        if (buffer.length < 4)
          return reject();
        const statuses = [
          new PrinterStatus(buffer[0]),
          new RollPaperSensorStatus(buffer[1]),
          new OfflineCauseStatus(buffer[2]),
          new ErrorCauseStatus(buffer[3])
        ];
        resolve(statuses);
      });
      [PrinterStatus, RollPaperSensorStatus, OfflineCauseStatus, ErrorCauseStatus].forEach((statusClass) => {
        statusClass.commands().forEach((command2) => {
          this.adapter.write(command2);
        });
      });
    });
  }
  setLogoAlignment(align = "lt") {
    switch (align) {
      case "lt":
      case "LT":
        this.buffer.write("0");
        break;
      case "ct":
      case "CT":
        this.buffer.write("1");
        break;
      case "rt":
      case "RT":
        this.buffer.write("2");
        break;
      default:
        this.buffer.write("0");
        break;
    }
    return this;
  }
  setLogoPrinting(pL_pH, fn, kc1, kc2, align = "lt") {
    if (kc1 < 32 || kc1 > 126) {
      throw new Error("Keycode 1 is out of range");
    } else if (kc2 < 32 || kc2 > 126) {
      throw new Error("Keycode 2 is out of range");
    }
    this.buffer.write(RECEIPT_ENHANCEMENT);
    this.buffer.write(pL_pH);
    this.buffer.write(fn);
    this.buffer.write("");
    this.buffer.writeUInt8(kc1);
    this.buffer.writeUInt8(kc2);
    this.setLogoAlignment(align);
    return this;
  }
  setTopLogoPrinting(kc1, kc2, align = "lt", space = 0) {
    if (space < 0 || space > 255) {
      throw new Error("Top logo space argument is out of range");
    }
    this.setLogoPrinting("\0", ">", kc1, kc2, align);
    this.buffer.writeUInt8(space);
    return this;
  }
  setBottomLogoPrinting(kc1, kc2, align = "lt") {
    this.setLogoPrinting("\0", "?", kc1, kc2, align);
    return this;
  }
  enableTopLogoPrinting(enable = true) {
    this.buffer.write(RECEIPT_ENHANCEMENT + "\0A");
    this.buffer.write("0");
    this.buffer.write(enable ? "0" : "1");
    return this;
  }
  enableBottomLogoPrinting(enable = true) {
    this.buffer.write(RECEIPT_ENHANCEMENT + "\0A");
    this.buffer.write("1");
    this.buffer.write(enable ? "0" : "1");
    return this;
  }
  starFullCut() {
    this.buffer.write(PAPER.STAR_FULL_CUT);
    return this;
  }
  emphasize() {
    this.buffer.write(TEXT_FORMAT.STAR_TXT_EMPHASIZED);
    return this;
  }
  cancelEmphasize() {
    this.buffer.write(TEXT_FORMAT.STAR_CANCEL_TXT_EMPHASIZED);
    return this;
  }
}
const command = _;

export { Image, Printer, command, Printer as default };
