import { rgbToHex } from '$lib/utils/rgbToHex';
import { shadeColor } from '$lib/utils/shadeColor';

/**
 * Represents a single stitch in the pattern.
 * @param {number} x - The absolute x position of the stitch.
 * @param {number} y - The absolute y position of the stitch.
 * @param {number} flags - Stitch flags (e.g. jump, trim).
 * @param {number} color - Index of the stitch color.
 * @constructor
 */
function Stitch(x, y, flags, color) {
  this.x = x;
  this.y = y;
  this.flags = flags;
  this.color = color;
}

/**
 * Represents a color with RGB components and an optional description.
 * @param {number} r - Red component (0-255).
 * @param {number} g - Green component (0-255).
 * @param {number} b - Blue component (0-255).
 * @param {string} description - Color description.
 * @constructor
 */
function Color(r, g, b, description) {
  this.r = r;
  this.g = g;
  this.b = b;
  this.description = description;
}

/**
 * Stitch type bit flags.
 * @readonly
 * @enum {number}
 */
const stitchTypes = {
  normal: 0,
  jump: 1,
  trim: 2,
  stop: 4,
  end: 8,
};

/**
 * Represents an embroidery pattern containing stitches and colors.
 * @constructor
 */
function Pattern() {
  /** @type {Color[]} */
  this.colors = [];

  /** @type {Stitch[]} */
  this.stitches = [];

  /** Hoop info (not typed, depends on implementation) */
  this.hoop = {};

  /** Last stitch position for relative moves */
  this.lastX = 0;
  this.lastY = 0;

  /** Bounding box */
  this.top = 0;
  this.bottom = 0;
  this.left = 0;
  this.right = 0;

  /** Current color index used for new stitches */
  this.currentColorIndex = 0;
}

/**
 * Adds a color by RGB values.
 * @param {number} r
 * @param {number} g
 * @param {number} b
 * @param {string} description
 */
Pattern.prototype.addColorRgb = function (r, g, b, description) {
  this.colors.push(new Color(r, g, b, description));
};

/**
 * Adds an existing Color instance.
 * @param {Color} color
 */
Pattern.prototype.addColor = function (color) {
  this.colors.push(color);
};

/**
 * Adds a stitch at absolute coordinates.
 * @param {number} x - Absolute X coordinate.
 * @param {number} y - Absolute Y coordinate.
 * @param {number} flags - Stitch flags.
 * @param {boolean} isAutoColorIndex - Whether to automatically increment color on stop.
 */
Pattern.prototype.addStitchAbs = function (x, y, flags, isAutoColorIndex) {
  if ((flags & stitchTypes.end) === stitchTypes.end) {
    this.calculateBoundingBox();
    this.fixColorCount();
  }
  if (
    (flags & stitchTypes.stop) === stitchTypes.stop &&
    this.stitches.length === 0
  ) {
    return;
  }
  if ((flags & stitchTypes.stop) === stitchTypes.stop && isAutoColorIndex) {
    this.currentColorIndex += 1;
  }
  this.stitches.push(new Stitch(x, y, flags, this.currentColorIndex));
};

/**
 * Adds a stitch relative to the last stitch.
 * @param {number} dx - Delta X from last stitch.
 * @param {number} dy - Delta Y from last stitch.
 * @param {number} flags - Stitch flags.
 * @param {boolean} [isAutoColorIndex=false] - Whether to automatically increment color on stop. Optional.
 */
Pattern.prototype.addStitchRel = function (dx, dy, flags, isAutoColorIndex) {
  if (typeof isAutoColorIndex === 'undefined') {
    isAutoColorIndex = false;
  }

  if (this.stitches.length !== 0) {
    const nx = this.lastX + dx;
    const ny = this.lastY + dy;
    this.lastX = nx;
    this.lastY = ny;
    this.addStitchAbs(nx, ny, flags, isAutoColorIndex);
  } else {
    this.addStitchAbs(dx, dy, flags, isAutoColorIndex);
  }
};

/**
 * Calculates the bounding box of all stitches, excluding trims.
 */
Pattern.prototype.calculateBoundingBox = function () {
  const stitchCount = this.stitches.length;
  if (stitchCount === 0) {
    this.bottom = 1;
    this.right = 1;
    return;
  }
  this.left = Infinity;
  this.top = Infinity;
  this.right = -Infinity;
  this.bottom = -Infinity;

  for (let i = 0; i < stitchCount; i++) {
    const pt = this.stitches[i];
    if (!(pt.flags & stitchTypes.trim)) {
      if (pt.x < this.left) this.left = pt.x;
      if (pt.y < this.top) this.top = pt.y;
      if (pt.x > this.right) this.right = pt.x;
      if (pt.y > this.bottom) this.bottom = pt.y;
    }
  }
};

/**
 * Moves all stitches so the pattern is positioned at positive coordinates.
 */
Pattern.prototype.moveToPositive = function () {
  const stitchCount = this.stitches.length;
  for (let i = 0; i < stitchCount; i++) {
    this.stitches[i].x -= this.left;
    this.stitches[i].y -= this.top;
  }
  this.right -= this.left;
  this.left = 0;
  this.bottom -= this.top;
  this.top = 0;
};

/**
 * Flips the pattern vertically.
 */
Pattern.prototype.invertPatternVertical = function () {
  const stitchCount = this.stitches.length;
  const tempTop = -this.top;
  for (let i = 0; i < stitchCount; i++) {
    this.stitches[i].y = -this.stitches[i].y;
  }
  this.top = -this.bottom;
  this.bottom = tempTop;
};

/**
 * Adds a random color to the pattern.
 */
Pattern.prototype.addColorRandom = function () {
  this.colors.push(
    new Color(
      Math.round(Math.random() * 256),
      Math.round(Math.random() * 256),
      Math.round(Math.random() * 256),
      'random',
    ),
  );
};

/**
 * Fixes the color count so colors match used indices in stitches.
 */
Pattern.prototype.fixColorCount = function () {
  let maxColorIndex = 0;
  const stitchCount = this.stitches.length;

  for (let i = 0; i < stitchCount; i++) {
    if (this.stitches[i].color > maxColorIndex) {
      maxColorIndex = this.stitches[i].color;
    }
  }

  while (this.colors.length <= maxColorIndex) {
    this.addColorRandom();
  }

  // Remove extra colors beyond max used index
  this.colors.splice(maxColorIndex + 1);
};

/**
 * Draws the stitch pattern to a canvas element.
 * @param {HTMLCanvasElement} canvas
 */
Pattern.prototype.drawShapeTo = function (canvas) {
  canvas.width = this.right;
  canvas.height = this.bottom;

  let gradient, tx, ty;
  let lastStitch = this.stitches[0];
  let gWidth = 100;

  if (canvas.getContext) {
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      // If context is null, just return or handle accordingly
      return;
    }

    ctx.lineWidth = 3;
    ctx.lineJoin = 'round';

    let color = this.colors[this.stitches[0].color];
    for (let i = 0; i < this.stitches.length; i++) {
      const currentStitch = this.stitches[i];
      if (i > 0) lastStitch = this.stitches[i - 1];
      tx = currentStitch.x - lastStitch.x;
      ty = currentStitch.y - lastStitch.y;

      gWidth = Math.sqrt(tx * tx + ty * ty);
      gradient = ctx.createRadialGradient(
        currentStitch.x - tx,
        currentStitch.y - ty,
        0,
        currentStitch.x - tx,
        currentStitch.y - ty,
        gWidth * 1.4,
      );

      gradient.addColorStop(0, shadeColor(rgbToHex(color), -60));
      gradient.addColorStop(0.05, rgbToHex(color));
      gradient.addColorStop(0.5, shadeColor(rgbToHex(color), 60));
      gradient.addColorStop(0.9, rgbToHex(color));
      gradient.addColorStop(1.0, shadeColor(rgbToHex(color), -60));

      ctx.strokeStyle = gradient;
      if (
        currentStitch.flags === stitchTypes.jump ||
        currentStitch.flags === stitchTypes.trim ||
        currentStitch.flags === stitchTypes.stop
      ) {
        color = this.colors[currentStitch.color];
        ctx.beginPath();
        ctx.strokeStyle =
          'rgba(' + color.r + ',' + color.g + ',' + color.b + ',0)';
        ctx.moveTo(currentStitch.x, currentStitch.y);
        ctx.stroke();
      }
      ctx.beginPath();
      ctx.moveTo(lastStitch.x, lastStitch.y);
      ctx.lineTo(currentStitch.x, currentStitch.y);
      ctx.stroke();
      lastStitch = currentStitch;
    }
  }
};

/**
 * Draws color swatches into a container element.
 * @param {HTMLElement} colorContainer
 */
Pattern.prototype.drawColorsTo = function (colorContainer) {
  this.colors.forEach((color) => {
    colorContainer.innerHTML += `<div style='background-color: rgb(${color.r}, ${color.g}, ${color.b}); height: 25px; width: 25px; border: 1px solid #000000; border-radius: 16px;'></div>`;
  });
};

/**
 * Displays the stitch count in a container element.
 * @param {HTMLElement} stitchesContainer
 * @param {string} stitchesString - Label for stitches count.
 */
Pattern.prototype.drawStitchesCountTo = function (
  stitchesContainer,
  stitchesString,
) {
  stitchesContainer.innerHTML += `<div><strong>${stitchesString}:</strong> ${this.stitches.length}</div>`;
};

/**
 * Displays pattern dimensions in a container element.
 * @param {HTMLElement} sizeContainer
 * @param {string} dimensionsString - Label for dimensions.
 */
Pattern.prototype.drawSizeValuesTo = function (
  sizeContainer,
  dimensionsString,
) {
  sizeContainer.innerHTML += `<div><strong>${dimensionsString}:</strong> ${Math.round(this.right / 10)}mm x ${Math.round(this.bottom / 10)}mm</div>`;
};

export { Pattern, Color, stitchTypes };
