import { describe, it, expect } from "vitest";
import {
  encodeStitchPosition,
  calculateLockDirection,
  generateLockStitches,
  encodeStitchesToPen,
  LOCK_STITCH_JUMP_SIZE,
} from "./encoder";
import { decodeAllPenStitches } from "./decoder";
import { STITCH, MOVE, TRIM, END } from "../import/constants";

// PEN format flag constants for testing
const PEN_FEED_DATA = 0x01;
const PEN_CUT_DATA = 0x02;

describe("encodeStitchPosition", () => {
  it("should encode position (0, 0) correctly", () => {
    const result = encodeStitchPosition(0, 0);
    expect(result).toEqual([0x00, 0x00, 0x00, 0x00]);
  });

  it("should shift coordinates left by 3 bits", () => {
    // Position (1, 1) should become (8, 8) after shifting
    const result = encodeStitchPosition(1, 1);
    expect(result).toEqual([0x08, 0x00, 0x08, 0x00]);
  });

  it("should handle negative coordinates", () => {
    // -1 in 16-bit signed = 0xFFFF, shifted left 3 = 0xFFF8
    const result = encodeStitchPosition(-1, -1);
    expect(result).toEqual([0xf8, 0xff, 0xf8, 0xff]);
  });

  it("should encode multi-byte coordinates correctly", () => {
    // Position (128, 0) -> shifted = 1024 = 0x0400
    const result = encodeStitchPosition(128, 0);
    expect(result).toEqual([0x00, 0x04, 0x00, 0x00]);
  });

  it("should round fractional coordinates", () => {
    const result = encodeStitchPosition(1.5, 2.4);
    // 2 << 3 = 16, 2 << 3 = 16
    expect(result).toEqual([0x10, 0x00, 0x10, 0x00]);
  });
});

describe("calculateLockDirection", () => {
  it("should look ahead for forward direction", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, STITCH, 0],
      [20, 0, STITCH, 0],
    ];

    const result = calculateLockDirection(stitches, 0, true);

    // Should accumulate forward stitches
    expect(result.dirX).toBeGreaterThan(0);
    expect(result.dirY).toBe(0);
    // Result should have magnitude ~8.0
    const magnitude = Math.sqrt(result.dirX ** 2 + result.dirY ** 2);
    expect(magnitude).toBeCloseTo(8.0, 1);
  });

  it("should look backward for backward direction", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, STITCH, 0],
      [20, 0, STITCH, 0],
    ];

    const result = calculateLockDirection(stitches, 2, false);

    // Should accumulate backward stitches
    expect(result.dirX).toBeLessThan(0);
    expect(result.dirY).toBe(0);
    // Result should have magnitude ~8.0
    const magnitude = Math.sqrt(result.dirX ** 2 + result.dirY ** 2);
    expect(magnitude).toBeCloseTo(8.0, 1);
  });

  it("should skip MOVE stitches when accumulating", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [5, 0, MOVE, 0], // Should be skipped
      [10, 0, STITCH, 0],
      [15, 0, STITCH, 0],
    ];

    const result = calculateLockDirection(stitches, 0, true);

    // Should skip the MOVE stitch and only count actual stitches
    expect(result.dirX).toBeGreaterThan(0);
  });

  it("should return fallback diagonal for empty or short stitch sequences", () => {
    const stitches = [[0, 0, STITCH, 0]];

    const result = calculateLockDirection(stitches, 0, true);

    // Should return diagonal fallback
    const expectedMag = 8.0 / Math.sqrt(2);
    expect(result.dirX).toBeCloseTo(expectedMag, 1);
    expect(result.dirY).toBeCloseTo(expectedMag, 1);
  });

  it("should normalize accumulated vector to magnitude 8.0", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [3, 4, STITCH, 0], // Distance = 5
      [6, 8, STITCH, 0], // Accumulated: (6, 8), length = 10
    ];

    const result = calculateLockDirection(stitches, 0, true);

    // Should normalize (6, 8) to magnitude 8.0
    // Expected: (6 * 8 / 10, 8 * 8 / 10) = (4.8, 6.4)
    expect(result.dirX).toBeCloseTo(4.8, 1);
    expect(result.dirY).toBeCloseTo(6.4, 1);

    const magnitude = Math.sqrt(result.dirX ** 2 + result.dirY ** 2);
    expect(magnitude).toBeCloseTo(8.0, 1);
  });

  it("should stop accumulating after reaching target length", () => {
    // Create a long chain of stitches
    const stitches = [
      [0, 0, STITCH, 0],
      [2, 0, STITCH, 0],
      [4, 0, STITCH, 0],
      [6, 0, STITCH, 0],
      [8, 0, STITCH, 0],
      [10, 0, STITCH, 0],
      [100, 0, STITCH, 0], // This should not be reached
    ];

    const result = calculateLockDirection(stitches, 0, true);

    // Should stop once accumulated length >= 8.0
    const magnitude = Math.sqrt(result.dirX ** 2 + result.dirY ** 2);
    expect(magnitude).toBeCloseTo(8.0, 1);
  });
});

describe("generateLockStitches", () => {
  it("should generate 8 lock stitches (32 bytes)", () => {
    const result = generateLockStitches(0, 0, 8.0, 0);
    expect(result.length).toBe(32); // 8 stitches * 4 bytes each
  });

  it("should alternate between +dir and -dir", () => {
    const result = generateLockStitches(0, 0, 8.0, 0);
    expect(result.length).toBe(32); // 8 stitches * 4 bytes

    // With a larger base position, verify the pattern still generates correctly
    const result2 = generateLockStitches(100, 100, 8.0, 0);
    expect(result2.length).toBe(32);
  });

  it("should rotate stitches in the given direction", () => {
    // Direction pointing right (8, 0)
    const result = generateLockStitches(0, 0, 8.0, 0);

    // Scale: 0.4 / 8.0 = 0.05
    // Scaled direction: (0.4, 0)
    // Positions should alternate between (+0.4, 0) and (-0.4, 0)

    expect(result.length).toBe(32);

    // With diagonal direction (8/√2, 8/√2)
    const diag = 8.0 / Math.sqrt(2);
    const result2 = generateLockStitches(0, 0, diag, diag);
    expect(result2.length).toBe(32);
  });
});

describe("encodeStitchesToPen", () => {
  it("should encode a simple stitch sequence", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, STITCH, 0],
      [20, 0, STITCH | END, 0], // Last stitch with both STITCH and END flags
    ];

    const result = encodeStitchesToPen(stitches);

    expect(result.penBytes.length).toBeGreaterThan(0);
    expect(result.penBytes.length % 4).toBe(0); // Should be multiple of 4 (4 bytes per stitch)
    expect(result.bounds.minX).toBe(0);
    expect(result.bounds.maxX).toBe(20);
  });

  it("should track bounds correctly", () => {
    const stitches = [
      [10, 20, STITCH, 0],
      [-5, 30, STITCH, 0],
      [15, -10, STITCH, 0],
      [0, 0, END, 0],
    ];

    const result = encodeStitchesToPen(stitches);

    expect(result.bounds.minX).toBe(-5);
    expect(result.bounds.maxX).toBe(15);
    expect(result.bounds.minY).toBe(-10);
    expect(result.bounds.maxY).toBe(30);
  });

  it("should mark the last stitch with DATA_END flag", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, END, 0],
    ];

    const result = encodeStitchesToPen(stitches);

    // Last stitch should have DATA_END (0x05) in low 3 bits of X coordinate
    const lastStitchStart = result.penBytes.length - 4;
    const xLow = result.penBytes[lastStitchStart];
    expect(xLow & 0x07).toBe(0x05); // DATA_END flag
  });

  it("should handle color changes with lock stitches", () => {
    const stitches = [
      [0, 0, STITCH, 0], // Color 0
      [10, 0, STITCH, 0], // Color 0
      [20, 0, STITCH, 0], // Color 0 - last stitch before color change
      [20, 0, STITCH, 1], // Color 1 - first stitch of new color
      [30, 0, STITCH, 1], // Color 1
      [40, 0, END, 1], // Color 1 - last stitch
    ];

    const result = encodeStitchesToPen(stitches);

    // Should include:
    // - Regular stitches for color 0 (3 stitches = 12 bytes)
    // - Finishing lock stitches (32 bytes)
    // - Cut command (4 bytes)
    // - COLOR_END marker (4 bytes)
    // - Starting lock stitches (32 bytes)
    // - Regular stitches for color 1 (3 stitches = 12 bytes)
    // Total: 96+ bytes

    expect(result.penBytes.length).toBeGreaterThan(90); // Should have many bytes from lock stitches
  });

  it("should encode color change sequence in correct order", () => {
    // Test the exact sequence of operations for a color change
    const stitches = [
      [0, 0, STITCH, 0], // Color 0
      [10, 0, STITCH, 0], // Color 0 - last stitch before color change
      [10, 0, STITCH, 1], // Color 1 - first stitch (same position)
      [20, 0, STITCH | END, 1], // Color 1 - last stitch
    ];

    const result = encodeStitchesToPen(stitches);
    const decoded = decodeAllPenStitches(result.penBytes);

    // Expected sequence:
    // 0. 8 starting lock stitches at (0, 0)
    // 1. Stitch at (0, 0) - color 0
    // 2. Stitch at (10, 0) - color 0 (last before change)
    // 3. 8 finishing lock stitches around (10, 0)
    // 4. Cut command at (10, 0)
    // 5. COLOR_END marker at (10, 0) - no jump needed since next color is at same position
    // 6. 8 starting lock stitches around (10, 0)
    // 7. Stitch at (10, 0) - color 1
    // 8. Stitch at (20, 0) - color 1 (last, with END flag)

    let idx = 0;

    // 1. First stitch (0, 0)
    expect(decoded[idx].x).toBe(0);
    expect(decoded[idx].y).toBe(0);
    expect(decoded[idx].isFeed).toBe(false);
    expect(decoded[idx].isCut).toBe(false);

    // 0. 8 starting lock stitches at (0, 0)
    for (; idx < 9; idx++) {
      expect(decoded[idx].x).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
      expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
    }

    // 2. Second stitch (10, 0) - last before color change
    expect(decoded[idx].x).toBe(10);
    expect(decoded[idx].y).toBe(0);
    idx++;

    // 3. 8 finishing lock stitches (should be around position 10, 0)
    for (let i = 0; i < 8; i++) {
      const lockStitch = decoded[idx];
      expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE); // Allow some deviation due to rotation
      expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
      expect(lockStitch.isFeed).toBe(false);
      expect(lockStitch.isCut).toBe(false);
      idx++;
    }

    // 4. COLOR_END + CUT command (combined on same stitch)
    const colorEndCutStitch = decoded[idx];
    expect(colorEndCutStitch.x).toBe(10);
    expect(colorEndCutStitch.y).toBe(0);
    expect(colorEndCutStitch.isCut).toBe(true);
    expect(colorEndCutStitch.isColorEnd).toBe(true);
    idx++;

    // 5. 8 starting lock stitches for new color
    for (let i = 0; i < 8; i++) {
      const lockStitch = decoded[idx];
      expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
      expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
      idx++;
    }

    // 6. First stitch of new color
    expect(decoded[idx].x).toBe(10);
    expect(decoded[idx].y).toBe(0);
    idx++;

    // 8. Last stitch with DATA_END flag
    expect(decoded[idx].x).toBe(20);
    expect(decoded[idx].y).toBe(0);
    expect(decoded[idx].isDataEnd).toBe(true);
  });

  it("should encode color change with jump in correct order", () => {
    // Test color change when next color is at a different position
    const stitches = [
      [0, 0, STITCH, 0], // Color 0
      [10, 0, STITCH, 0], // Color 0 - last before change
      [30, 10, STITCH, 1], // Color 1 - different position, requires jump
      [40, 10, STITCH | END, 1],
    ];

    const result = encodeStitchesToPen(stitches);
    const decoded = decodeAllPenStitches(result.penBytes);

    let idx = 10; // Skip 8 starting locks + first two regular stitches

    // After second stitch, should have:
    // 1. 8 finishing lock stitches at (10, 0)
    for (let i = 0; i < 8; i++) {
      expect(decoded[idx].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
      expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
      idx++;
    }

    // 2. COLOR_END + CUT command at (10, 0) - combined on same stitch
    expect(decoded[idx].x).toBe(10);
    expect(decoded[idx].y).toBe(0);
    expect(decoded[idx].isCut).toBe(true);
    expect(decoded[idx].isColorEnd).toBe(true);
    idx++;

    // 3. Jump to new position (30, 10)
    expect(decoded[idx].x).toBe(30);
    expect(decoded[idx].y).toBe(10);
    expect(decoded[idx].isFeed).toBe(true);
    idx++;

    // 4. 8 starting lock stitches at (30, 10)
    for (let i = 0; i < 8; i++) {
      expect(decoded[idx].x).to.be.closeTo(30, LOCK_STITCH_JUMP_SIZE);
      expect(decoded[idx].y).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
      idx++;
    }

    // 5. Continue with new color stitches
    expect(decoded[idx].x).toBe(30);
    expect(decoded[idx].y).toBe(10);
  });

  it("should encode color change followed by explicit JUMP in correct order", () => {
    // Test when PES data has a JUMP stitch immediately after color change
    // This is a common pattern: color change, then jump to new location
    const stitches = [
      [0, 0, STITCH, 0], // Color 0
      [10, 0, STITCH, 0], // Color 0 - last before change
      [50, 20, MOVE, 1], // Color 1 - JUMP to new location (50, 20)
      [50, 20, STITCH, 1], // Color 1 - first actual stitch at new location
      [60, 20, STITCH | END, 1],
    ];

    const result = encodeStitchesToPen(stitches);
    const decoded = decodeAllPenStitches(result.penBytes);

    // Expected sequence:
    // 1. Stitch at (0, 0) - color 0
    // 2. Stitch at (10, 0) - color 0 (last before change)
    // 3. 8 finishing lock stitches at (10, 0)
    // 4. Cut command at (10, 0)
    // 5. Jump to (50, 20) - from MOVE stitch (the encoder skips this MOVE in the loop with i++)
    // 6. COLOR_END marker at (50, 20)
    // 7. 8 starting lock stitches at (50, 20)
    // 8. First stitch of new color at (50, 20)
    // 9. Last stitch at (60, 20) with END flag

    let idx = 0;

    // 1-2. First two stitches
    expect(decoded[idx++].x).toBe(0);
    idx = 9; // skip startingLock
    expect(decoded[idx++].x).toBe(10);

    // 3. 8 finishing lock stitches at (10, 0)
    for (let i = 0; i < 8; i++) {
      expect(decoded[idx].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
      expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
      idx++;
    }

    // 4. COLOR_END + CUT command at (10, 0) - combined on same stitch
    expect(decoded[idx].x).toBe(10);
    expect(decoded[idx].y).toBe(0);
    expect(decoded[idx].isCut).toBe(true);
    expect(decoded[idx].isColorEnd).toBe(true);
    idx++;

    // 5. Jump to new location (50, 20) - extracted from the MOVE stitch
    expect(decoded[idx].x).toBe(50);
    expect(decoded[idx].y).toBe(20);
    expect(decoded[idx].isFeed).toBe(true);
    idx++;

    // 6. 8 starting lock stitches at (50, 20)
    for (let i = 0; i < 8; i++) {
      expect(decoded[idx].x).to.be.closeTo(50, LOCK_STITCH_JUMP_SIZE);
      expect(decoded[idx].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE);
      idx++;
    }

    // 7. First actual stitch of new color at (50, 20)
    expect(decoded[idx].x).toBe(50);
    expect(decoded[idx].y).toBe(20);
    expect(decoded[idx].isFeed).toBe(false);
    idx++;

    // 8. Last stitch with DATA_END
    expect(decoded[idx].x).toBe(60);
    expect(decoded[idx].y).toBe(20);
    expect(decoded[idx].isDataEnd).toBe(true);
  });

  it("should handle long jumps with lock stitches and cut in correct order", () => {
    // Test the exact sequence for a long jump (distance > 50)
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, STITCH, 0],
      [100, 0, MOVE, 0], // Long jump (distance = 90 > 50)
      [110, 0, STITCH, 0],
      [120, 0, STITCH | END, 0],
    ];

    const result = encodeStitchesToPen(stitches);
    const decoded = decodeAllPenStitches(result.penBytes);

    // Expected sequence:
    // 1. Stitch at (0, 0)
    // 2. Stitch at (10, 0)
    // 3. 8 finishing lock stitches at (10, 0)
    // 4. Jump to (100, 0) with FEED and CUT flags
    // 5. 8 starting lock stitches at (100, 0)
    // 6. Stitch at (110, 0)
    // 7. Stitch at (120, 0) with END flag

    let idx = 0;

    // 1-2. First two stitches
    expect(decoded[idx++].x).toBe(0);
    idx = 9; // Skip 8 starting lock stitches
    expect(decoded[idx++].x).toBe(10);

    // 3. 8 finishing lock stitches at (10, 0)
    for (let i = 0; i < 8; i++) {
      const lockStitch = decoded[idx];
      expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
      expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
      expect(lockStitch.isFeed).toBe(false);
      expect(lockStitch.isCut).toBe(false);
      idx++;
    }

    // 4. Jump to (100, 0) with BOTH FEED and CUT flags
    const jumpStitch = decoded[idx];
    expect(jumpStitch.x).toBe(100);
    expect(jumpStitch.y).toBe(0);
    expect(jumpStitch.isFeed).toBe(true);
    expect(jumpStitch.isCut).toBe(true);
    expect(jumpStitch.yFlags).toBe(PEN_FEED_DATA | PEN_CUT_DATA); // 0x03
    idx++;

    // 5. 8 starting lock stitches at (100, 0)
    for (let i = 0; i < 8; i++) {
      const lockStitch = decoded[idx];
      expect(lockStitch.x).to.be.closeTo(100, LOCK_STITCH_JUMP_SIZE);
      expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
      idx++;
    }

    // 6-7. Final two stitches
    expect(decoded[idx].x).toBe(110);
    expect(decoded[idx].y).toBe(0);
    idx++;

    expect(decoded[idx].x).toBe(120);
    expect(decoded[idx].isDataEnd).toBe(true);
  });

  it("should encode MOVE flag for jump stitches", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, MOVE, 0], // Short jump (no lock stitches)
      [20, 0, END, 0],
    ];

    const result = encodeStitchesToPen(stitches);

    // Second stitch (jump) should have FEED_DATA flag (0x01) in Y low byte
    // Stitch format: [xLow, xHigh, yLow, yHigh]
    // We need to find the jump stitch - it's the second one encoded
    const jumpStitchStart = 36; // Skip 8 starting locks (32 bytes) + first stitch (4 bytes)
    const yLow = result.penBytes[jumpStitchStart + 2];
    expect(yLow & 0x01).toBe(0x01); // FEED_DATA flag
  });

  it("should not include MOVE stitches in bounds calculation", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [100, 100, MOVE, 0], // Jump - should not affect bounds
      [10, 10, STITCH, 0],
      [20, 20, STITCH | END, 0], // Last stitch with both STITCH and END flags
    ];

    const result = encodeStitchesToPen(stitches);

    // Bounds should only include STITCH positions, not MOVE
    expect(result.bounds.minX).toBe(0);
    expect(result.bounds.maxX).toBe(20);
    expect(result.bounds.minY).toBe(0);
    expect(result.bounds.maxY).toBe(20);
  });

  it("should handle TRIM flag correctly", () => {
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, TRIM, 0],
      [20, 0, STITCH | END, 0],
    ];

    const result = encodeStitchesToPen(stitches);
    const decoded = decodeAllPenStitches(result.penBytes);

    let idx = 0;

    // Verify sequence:
    // 1. Regular stitch at (0, 0)
    const firstIdx = idx;
    expect(decoded[idx++].x).toBe(0);
    idx = 9; // Skip 8 starting lock stitches
    expect(decoded[firstIdx].isCut).toBe(false);

    // 2. TRIM command at (10, 0) - should have CUT flag
    const trimIdx = idx;
    expect(decoded[idx++].x).toBe(10);
    expect(decoded[trimIdx].y).toBe(0);
    expect(decoded[trimIdx].isCut).toBe(true);
    expect(decoded[trimIdx].isFeed).toBe(false); // TRIM doesn't include FEED
    expect(decoded[trimIdx].yFlags).toBe(PEN_CUT_DATA); // Only CUT flag

    // 3. Final stitch with DATA_END
    expect(decoded[idx].x).toBe(20);
    expect(decoded[idx].isDataEnd).toBe(true);
  });

  it("should handle empty stitch array", () => {
    const stitches: number[][] = [];

    const result = encodeStitchesToPen(stitches);

    expect(result.penBytes.length).toBe(0);
    expect(result.bounds.minX).toBe(0);
    expect(result.bounds.maxX).toBe(0);
    expect(result.bounds.minY).toBe(0);
    expect(result.bounds.maxY).toBe(0);
  });

  it("should handle single stitch", () => {
    const stitches = [[5, 10, END, 0]];

    const result = encodeStitchesToPen(stitches);

    expect(result.penBytes.length).toBe(36); // 8 starting locks (32 bytes) + 1 stitch (4 bytes)
    expect(result.bounds.minX).toBe(5);
    expect(result.bounds.maxX).toBe(5);
    expect(result.bounds.minY).toBe(10);
    expect(result.bounds.maxY).toBe(10);
    // END stitches update bounds (they're not MOVE stitches)
  });

  it("should add DATA_END flag to last stitch even without END flag in input", () => {
    // Test that the encoder automatically marks the last stitch with DATA_END
    // even if the input stitches don't have an END flag
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, STITCH, 0],
      [20, 0, STITCH, 0], // Last stitch - NO END flag
    ];

    const result = encodeStitchesToPen(stitches);
    const decoded = decodeAllPenStitches(result.penBytes);

    // First two stitches (after 8 starting locks) should NOT have DATA_END flag
    expect(decoded[8].isDataEnd).toBe(false);
    expect(decoded[9].isDataEnd).toBe(false);

    // Last stitch SHOULD have DATA_END flag automatically added
    expect(decoded[10].isDataEnd).toBe(true);
    expect(decoded[10].x).toBe(20);
    expect(decoded[10].y).toBe(0);
  });

  it("should add DATA_END flag when input has explicit END flag", () => {
    // Verify that END flag in input also results in DATA_END flag in output
    const stitches = [
      [0, 0, STITCH, 0],
      [10, 0, STITCH, 0],
      [20, 0, STITCH | END, 0], // Explicit END flag
    ];

    const result = encodeStitchesToPen(stitches);
    const decoded = decodeAllPenStitches(result.penBytes);

    // Last stitch should have DATA_END flag
    expect(decoded[10].isDataEnd).toBe(true);
    expect(decoded[10].x).toBe(20);
    expect(decoded[10].y).toBe(0);
  });

  it("should add lock stitches at the very start of the pattern", () => {
    // Matching C# behavior: Nuihajime_TomeDataPlus is called when counter <= 2
    // This adds starting lock stitches to secure the thread at pattern start
    const stitches = [
      [10, 20, MOVE, 0],
      [20, 20, STITCH, 0],
      [30, 20, STITCH | END, 0],
    ];

    const result = encodeStitchesToPen(stitches);
    const decoded = decodeAllPenStitches(result.penBytes);

    // Expected sequence:
    // 0. Feed move to proper location (should always happen here)
    // 1. 8 starting lock stitches at (10, 20)
    // 2. First actual stitch at (10, 20)
    // 3. Second stitch at (20, 20)
    // 4. Last stitch at (30, 20)
    expect(decoded[0].x).toBe(10);
    expect(decoded[0].y).toBe(20);
    expect(decoded[0].isFeed).toBe(true);

    // First 8 stitches should be lock stitches around the starting position
    for (let i = 1; i < 1 + 8; i++) {
      expect(decoded[i].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
      expect(decoded[i].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE);
      expect(decoded[i].isFeed).toBe(false);
      expect(decoded[i].isCut).toBe(false);
    }

    // Then the actual stitches
    expect(decoded[9].x).toBe(20);
    expect(decoded[9].y).toBe(20);
    expect(decoded[10].x).toBe(30);
    expect(decoded[10].y).toBe(20);
    expect(decoded[10].isDataEnd).toBe(true);
  });
});
