import { Color, stitchTypes } from '$lib/file-renderer/pattern';

/** @type {Color[]} */
const colors = [
  new Color(0, 0, 0, 'Black'),
  new Color(0, 0, 0, 'Black'),
  new Color(255, 255, 255, 'White'),
  new Color(255, 255, 23, 'Yellow'),
  new Color(250, 160, 96, 'Orange'),
  new Color(92, 118, 73, 'Olive Green'),
  new Color(64, 192, 48, 'Green'),
  new Color(101, 194, 200, 'Sky'),
  new Color(172, 128, 190, 'Purple'),
  new Color(245, 188, 203, 'Pink'),
  new Color(255, 0, 0, 'Red'),
  new Color(192, 128, 0, 'Brown'),
  new Color(0, 0, 240, 'Blue'),
  new Color(228, 195, 93, 'Gold'),
  new Color(165, 42, 42, 'Dark Brown'),
  new Color(213, 176, 212, 'Pale Violet'),
  new Color(252, 242, 148, 'Pale Yellow'),
  new Color(240, 208, 192, 'Pale Pink'),
  new Color(255, 192, 0, 'Peach'),
  new Color(201, 164, 128, 'Beige'),
  new Color(155, 61, 75, 'Wine Red'),
  new Color(160, 184, 204, 'Pale Sky'),
  new Color(127, 194, 28, 'Yellow Green'),
  new Color(185, 185, 185, 'Silver Grey'),
  new Color(160, 160, 160, 'Grey'),
  new Color(152, 214, 189, 'Pale Aqua'),
  new Color(184, 240, 240, 'Baby Blue'),
  new Color(54, 139, 160, 'Powder Blue'),
  new Color(79, 131, 171, 'Bright Blue'),
  new Color(56, 106, 145, 'Slate Blue'),
  new Color(0, 32, 107, 'Nave Blue'),
  new Color(229, 197, 202, 'Salmon Pink'),
  new Color(249, 103, 107, 'Coral'),
  new Color(227, 49, 31, 'Burnt Orange'),
  new Color(226, 161, 136, 'Cinnamon'),
  new Color(181, 148, 116, 'Umber'),
  new Color(228, 207, 153, 'Blonde'),
  new Color(225, 203, 0, 'Sunflower'),
  new Color(225, 173, 212, 'Orchid Pink'),
  new Color(195, 0, 126, 'Peony Purple'),
  new Color(128, 0, 75, 'Burgundy'),
  new Color(160, 96, 176, 'Royal Purple'),
  new Color(192, 64, 32, 'Cardinal Red'),
  new Color(202, 224, 192, 'Opal Green'),
  new Color(137, 152, 86, 'Moss Green'),
  new Color(0, 170, 0, 'Meadow Green'),
  new Color(33, 138, 33, 'Dark Green'),
  new Color(93, 174, 148, 'Aquamarine'),
  new Color(76, 191, 143, 'Emerald Green'),
  new Color(0, 119, 114, 'Peacock Green'),
  new Color(112, 112, 112, 'Dark Grey'),
  new Color(242, 255, 255, 'Ivory White'),
  new Color(177, 88, 24, 'Hazel'),
  new Color(203, 138, 7, 'Toast'),
  new Color(247, 146, 123, 'Salmon'),
  new Color(152, 105, 45, 'Cocoa Brown'),
  new Color(162, 113, 72, 'Sienna'),
  new Color(123, 85, 74, 'Sepia'),
  new Color(79, 57, 70, 'Dark Sepia'),
  new Color(82, 58, 151, 'Violet Blue'),
  new Color(0, 0, 160, 'Blue Ink'),
  new Color(0, 150, 222, 'Solar Blue'),
  new Color(178, 221, 83, 'Green Dust'),
  new Color(250, 143, 187, 'Crimson'),
  new Color(222, 100, 158, 'Floral Pink'),
  new Color(181, 80, 102, 'Wine'),
  new Color(94, 87, 71, 'Olive Drab'),
  new Color(76, 136, 31, 'Meadow'),
  new Color(228, 220, 121, 'Mustard'),
  new Color(203, 138, 26, 'Yellow Ochre'),
  new Color(198, 170, 66, 'Old Gold'),
  new Color(236, 176, 44, 'Honeydew'),
  new Color(248, 128, 64, 'Tangerine'),
  new Color(255, 229, 5, 'Canary Yellow'),
  new Color(250, 122, 122, 'Vermillion'),
  new Color(107, 224, 0, 'Bright Green'),
  new Color(56, 108, 174, 'Ocean Blue'),
  new Color(227, 196, 180, 'Beige Grey'),
  new Color(227, 172, 129, 'Bamboo'),
];

/**
 * Decode a single byte for JEF stitch data (signed int8-like with special encoding).
 * @param {number} byte
 * @returns {number}
 */
const jefDecode = (byte) => (byte >= 0x80 ? -(~byte & 0xff) - 1 : byte);

/**
 * Check if a byte represents a special stitch (0x80).
 * @param {number} byte
 * @returns {boolean}
 */
const isSpecialStitch = (byte) => byte === 0x80;

/**
 * Check if a byte represents a stop or trim command.
 * @param {number} byte
 * @returns {boolean}
 */
const isStopOrTrim = (byte) =>
  (byte & 0x01) !== 0 || byte === 0x02 || byte === 0x04;

/**
 * Check if a byte indicates end of pattern.
 * @param {number} byte
 * @returns {boolean}
 */
const isEndOfPattern = (byte) => byte === 0x10;

/**
 * Check if a byte indicates a stop command.
 * @param {number} byte
 * @returns {boolean}
 */
const isStop = (byte) => (byte & 0x01) !== 0;

/**
 * Read two stitch data bytes from the file.
 * @param {EmbroideryFileView} file
 * @returns {{ byte1: number, byte2: number }}
 */
const readStitchData = (file) => ({
  byte1: file.getUint8(),
  byte2: file.getUint8(),
});

/**
 * Add colors from file data to the pattern.
 * @param {EmbroideryFileView} file
 * @param {EmbroideryPattern} pattern
 * @param {number} colorCount
 */
const addColorsToPattern = (file, pattern, colorCount) => {
  for (let i = 0; i < colorCount; i++) {
    const colorIndex = file.getUint32(file.tell(), true) % colors.length;
    pattern.addColor(colors[colorIndex]);
  }
};

/**
 * Determine the stitch type and potentially read additional bytes.
 * @param {EmbroideryFileView} file
 * @param {number} byte1
 * @param {number} byte2
 * @returns {{ type: number, byte1: number, byte2: number, end?: boolean }}
 */
const determineStitchType = (file, byte1, byte2) => {
  if (isSpecialStitch(byte1)) {
    if (isStopOrTrim(byte2)) {
      return {
        type: isStop(byte2) ? stitchTypes.stop : stitchTypes.trim,
        byte1: file.getUint8(),
        byte2: file.getUint8(),
      };
    } else if (isEndOfPattern(byte2)) {
      return { type: stitchTypes.end, byte1: 0, byte2: 0, end: true };
    }
  }
  return { type: stitchTypes.normal, byte1, byte2 };
};

/**
 * Process stitches in the file and add them to the pattern.
 * @param {EmbroideryFileView} file
 * @param {EmbroideryPattern} pattern
 * @param {number} stitchCount
 */
const processStitches = (file, pattern, stitchCount) => {
  let stitchesProcessed = 0;
  while (stitchesProcessed < stitchCount + 100) {
    const { byte1, byte2 } = readStitchData(file);
    const {
      type,
      byte1: decodedByte1,
      byte2: decodedByte2,
      end,
    } = determineStitchType(file, byte1, byte2);
    pattern.addStitchRel(
      jefDecode(decodedByte1),
      jefDecode(decodedByte2),
      type,
      true,
    );
    if (end) break;
    stitchesProcessed++;
  }
};

/**
 * Reads a JEF file and adds stitches and colors to the pattern.
 * @param {EmbroideryFileView} file
 * @param {EmbroideryPattern} pattern
 */
export function jefRead(file, pattern) {
  file.seek(24);
  const colorCount = file.getInt32(file.tell(), true);
  const stitchCount = file.getInt32(file.tell(), true);
  file.seek(file.tell() + 84);

  addColorsToPattern(file, pattern, colorCount);
  file.seek(file.tell() + (6 - colorCount) * 4);

  processStitches(file, pattern, stitchCount);
  pattern.invertPatternVertical();
}

export const jefColors = colors;
