'use strict';

/**
 * TinyColor Polyfill - A lightweight color manipulation utility.
 *
 * This is a drop-in replacement for tinycolor2, implementing only the methods
 * used in the Vega theme overriding system.
 *
 * @see VD-5026 - [Zero Dependencies] remove tinycolor2 dependency
 */
/**
 * TinyColor polyfill class for color manipulation.
 * Implements the same interface as tinycolor2.Instance for the methods used in Vega.
 */
class TinyColorPolyfill {
  /**
   * Create a new TinyColorPolyfill instance.
   *
   * @param {string | RgbaValue} color - The color to parse.
   */
  constructor(color) {
    const rgba = TinyColorPolyfill.parseColor(color);
    this._r = rgba.r;
    this._g = rgba.g;
    this._b = rgba.b;
    this._a = rgba.a;
  }
  // ==================== Private Static Helper Methods ====================
  /**
   * Clamp a value between a minimum and maximum.
   *
   * @param {number} value - The value to clamp.
   * @param {number} min - The minimum allowed value.
   * @param {number} max - The maximum allowed value.
   * @returns {number} The clamped value.
   */
  static clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }
  /**
   * Parse a color string or RGBA object into an RgbaValue.
   *
   * Supported formats:
   * - Hex: #RGB, #RRGGBB, #RRGGBBAA
   * - RGB: rgb(r, g, b)
   * - RGBA: rgba(r, g, b, a)
   * - Object: { r, g, b, a? }
   *
   * @param {string | RgbaValue} color - The color to parse.
   * @returns {RgbaValue} The parsed RGBA value.
   */
  static parseColor(color) {
    if (typeof color === 'object' && 'r' in color && 'g' in color && 'b' in color) {
      return {
        r: Math.round(TinyColorPolyfill.clamp(color.r, 0, 255)),
        g: Math.round(TinyColorPolyfill.clamp(color.g, 0, 255)),
        b: Math.round(TinyColorPolyfill.clamp(color.b, 0, 255)),
        a: color.a !== undefined ? TinyColorPolyfill.clamp(color.a, 0, 1) : 1,
      };
    }
    if (typeof color !== 'string') {
      return { r: 0, g: 0, b: 0, a: 1 };
    }
    const trimmed = color.trim().toLowerCase();
    // Hex format
    if (trimmed.startsWith('#')) {
      return TinyColorPolyfill.parseHex(trimmed);
    }
    // RGB/RGBA format
    if (trimmed.startsWith('rgb')) {
      return TinyColorPolyfill.parseRgbString(trimmed);
    }
    // Fallback to black for unsupported formats
    return { r: 0, g: 0, b: 0, a: 1 };
  }
  /* eslint-disable spellcheck/spell-checker */
  /**
   * Parse a hex color string into RGBA values.
   *
   * @param {string} hex - The hex color string (e.g., "#fff", "#ffffff", "#ffffffff").
   * @returns {RgbaValue} The parsed RGBA value.
   */
  static parseHex(hex) {
    /* eslint-enable spellcheck/spell-checker */
    // Remove the # prefix
    let hexValue = hex.slice(1);
    // Expand shorthand format (#RGB -> #RRGGBB)
    if (hexValue.length === 3) {
      hexValue = hexValue
        .split('')
        .map((c) => c + c)
        .join('');
    }
    // Handle #RGBA shorthand
    if (hexValue.length === 4) {
      hexValue = hexValue
        .split('')
        .map((c) => c + c)
        .join('');
    }
    const r = parseInt(hexValue.slice(0, 2), 16);
    const g = parseInt(hexValue.slice(2, 4), 16);
    const b = parseInt(hexValue.slice(4, 6), 16);
    let a = 1;
    // Handle #RRGGBBAA format
    if (hexValue.length === 8) {
      a = parseInt(hexValue.slice(6, 8), 16) / 255;
    }
    return {
      r: Number.isNaN(r) ? 0 : r,
      g: Number.isNaN(g) ? 0 : g,
      b: Number.isNaN(b) ? 0 : b,
      a: Number.isNaN(a) ? 1 : a,
    };
  }
  /**
   * Parse an RGB or RGBA string into RGBA values.
   *
   * @param {string} rgbString - The RGB/RGBA string (e.g., "rgb(255, 128, 0)" or "rgba(255, 128, 0, 0.5)").
   * @returns {RgbaValue} The parsed RGBA value.
   */
  static parseRgbString(rgbString) {
    // Match rgb(r, g, b) or rgba(r, g, b, a) with comma-separated integer values.
    // Note: Does NOT support percentage values (e.g., rgb(100%, 50%, 0%)) or
    // modern CSS space-separated syntax (e.g., rgb(255 128 0 / 0.5)).
    const match = rgbString.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d+\.?\d*))?\s*\)/);
    if (!match) {
      return { r: 0, g: 0, b: 0, a: 1 };
    }
    return {
      r: TinyColorPolyfill.clamp(parseInt(match[1], 10), 0, 255),
      g: TinyColorPolyfill.clamp(parseInt(match[2], 10), 0, 255),
      b: TinyColorPolyfill.clamp(parseInt(match[3], 10), 0, 255),
      a: match[4] !== undefined ? TinyColorPolyfill.clamp(parseFloat(match[4]), 0, 1) : 1,
    };
  }
  /**
   * Convert RGB to HSL.
   *
   * @param {number} r - Red (0-255).
   * @param {number} g - Green (0-255).
   * @param {number} b - Blue (0-255).
   * @returns {HslValue} The HSL values (h: 0-360, s: 0-1, l: 0-1).
   */
  static rgbToHsl(r, g, b) {
    r /= 255;
    g /= 255;
    b /= 255;
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    const l = (max + min) / 2;
    let h = 0;
    let s = 0;
    if (max !== min) {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch (max) {
        case r:
          h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
          break;
        case g:
          h = ((b - r) / d + 2) / 6;
          break;
        case b:
          h = ((r - g) / d + 4) / 6;
          break;
      }
    }
    return { h: h * 360, s, l };
  }
  /**
   * Convert HSL to RGB.
   *
   * @param {number} h - Hue (0-360).
   * @param {number} s - Saturation (0-1).
   * @param {number} l - Lightness (0-1).
   * @returns {RgbValue} The RGB values (0-255).
   */
  static hslToRgb(h, s, l) {
    h /= 360;
    let r;
    let g;
    let b;
    if (s === 0) {
      r = g = b = l;
    }
    else {
      /**
       * Helper function to convert hue to RGB component.
       *
       * @param {number} p - First intermediate value.
       * @param {number} q - Second intermediate value.
       * @param {number} t - Hue offset value.
       * @returns {number} RGB component value (0-1).
       */
      const hue2rgb = (p, q, t) => {
        if (t < 0)
          t += 1;
        if (t > 1)
          t -= 1;
        if (t < 1 / 6)
          return p + (q - p) * 6 * t;
        if (t < 1 / 2)
          return q;
        if (t < 2 / 3)
          return p + (q - p) * (2 / 3 - t) * 6;
        return p;
      };
      const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
      const p = 2 * l - q;
      r = hue2rgb(p, q, h + 1 / 3);
      g = hue2rgb(p, q, h);
      b = hue2rgb(p, q, h - 1 / 3);
    }
    return {
      r: Math.round(r * 255),
      g: Math.round(g * 255),
      b: Math.round(b * 255),
    };
  }
  /**
   * Calculate the perceived brightness of a color using the YIQ formula.
   * This matches tinycolor2's brightness calculation.
   *
   * @param {number} r - Red (0-255).
   * @param {number} g - Green (0-255).
   * @param {number} b - Blue (0-255).
   * @returns {number} Brightness value (0-255).
   */
  static getBrightness(r, g, b) {
    return (r * 299 + g * 587 + b * 114) / 1000;
  }
  // ==================== Public Instance Methods ====================
  /**
   * Check if the color is dark (brightness < 128).
   *
   * @returns {boolean} True if the color is considered dark.
   */
  isDark() {
    return TinyColorPolyfill.getBrightness(this._r, this._g, this._b) < 128;
  }
  /**
   * Check if the color is light (brightness >= 128).
   *
   * @returns {boolean} True if the color is considered light.
   */
  isLight() {
    return !this.isDark();
  }
  /**
   * Create a copy of this color.
   *
   * @returns {TinyColorPolyfill} A new TinyColorPolyfill instance with the same values.
   */
  clone() {
    const cloned = new TinyColorPolyfill({
      r: this._r,
      g: this._g,
      b: this._b,
      a: this._a,
    });
    return cloned;
  }
  /**
   * Lighten the color by a given amount.
   * Note: tinycolor2 accepts negative values to darken.
   *
   * @param {number} amount - The amount to lighten (0-100). Negative values darken.
   * @returns {TinyColorPolyfill} This instance for chaining.
   */
  lighten(amount = 10) {
    const hsl = TinyColorPolyfill.rgbToHsl(this._r, this._g, this._b);
    hsl.l = TinyColorPolyfill.clamp(hsl.l + amount / 100, 0, 1);
    const rgb = TinyColorPolyfill.hslToRgb(hsl.h, hsl.s, hsl.l);
    this._r = rgb.r;
    this._g = rgb.g;
    this._b = rgb.b;
    return this;
  }
  /**
   * Darken the color by a given amount.
   *
   * @param {number} amount - The amount to darken (0-100).
   * @returns {TinyColorPolyfill} This instance for chaining.
   */
  darken(amount = 10) {
    return this.lighten(-amount);
  }
  /**
   * Set the alpha (opacity) of the color.
   *
   * @param {number} alpha - The alpha value (0-1).
   * @returns {TinyColorPolyfill} This instance for chaining.
   */
  setAlpha(alpha) {
    this._a = TinyColorPolyfill.clamp(alpha, 0, 1);
    return this;
  }
  /**
   * Get the RGBA representation of the color.
   *
   * @returns {RgbaValue} The RGBA values.
   */
  toRgb() {
    return {
      r: this._r,
      g: this._g,
      b: this._b,
      a: this._a,
    };
  }
}
/**
 * Factory function to create a TinyColorPolyfill instance.
 * This provides API compatibility with tinycolor2's default export.
 *
 * @param {string | RgbaValue} color - The color to parse.
 * @returns {TinyColorPolyfill} A new TinyColorPolyfill instance.
 */
function tinycolor(color) {
  return new TinyColorPolyfill(color);
}

exports.TinyColorPolyfill = TinyColorPolyfill;
exports.tinycolor = tinycolor;
