/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { GeneralDiagnosticsBehavior } from "#behaviors/general-diagnostics";
import { OnOffServer } from "#behaviors/on-off";
import { ColorControl } from "#clusters/color-control";
import { GeneralDiagnostics } from "#clusters/general-diagnostics";
import { RootEndpoint } from "#endpoints/root";
import { addValueWithOverflow, cropValueRange, ImplementationError, Logger, MaybePromise, Time } from "#general";
import { ClusterType, StatusCode, StatusResponseError } from "#types";
import { ColorControlBehavior } from "./ColorControlBehavior.js";
import {
  hsvToMireds,
  hsvToXy,
  kelvinToMireds,
  miredsToHsv,
  miredsToKelvin,
  miredsToXy,
  xyToMireds
} from "./ColorConversionUtils.js";
const logger = Logger.get("ColorControlServer");
const ColorControlServerBase = ColorControlBehavior.with(
  ColorControl.Feature.HueSaturation,
  ColorControl.Feature.EnhancedHue,
  ColorControl.Feature.ColorLoop,
  ColorControl.Feature.Xy,
  ColorControl.Feature.ColorTemperature
);
const MIN_CIE_XY_VALUE = 0;
const MAX_CIE_XY_VALUE = 65279;
const MIN_TEMPERATURE_VALUE = 0;
const MAX_TEMPERATURE_VALUE = 65279;
const MIN_HUE_VALUE = 0;
const MAX_HUE_VALUE = 254;
const MAX_ENHANCED_HUE_VALUE = 65535;
const MIN_SATURATION_VALUE = 0;
const MAX_SATURATION_VALUE = 254;
const MIN_CURRENT_LEVEL = 1;
const MAX_CURRENT_LEVEL = 254;
class ColorControlServerLogic extends ColorControlServerBase {
  /*
   * The following block contains some convenience methods to allow to easily work with the CIE color space values
   * as usual and hide the matter specific data model.
   */
  /** Returns the current X value as decimal number according to CIE in the range 0..1. */
  get x() {
    return this.#returnAsXyValue(this.state.currentX);
  }
  /**
   * Sets the current X value as decimal number according to CIE in the range 0..1 and converts into Matter number
   * range.
   */
  set x(value) {
    this.#setFromXyValue("currentX", value);
  }
  /** Returns the current Y value as decimal number according to CIE in the range 0..1. */
  get y() {
    return this.#returnAsXyValue(this.state.currentY);
  }
  /**
   * Sets the current Y value as decimal number according to CIE in the range 0..1 and converts into Matter number
   * range.
   */
  set y(value) {
    this.#setFromXyValue("currentY", value);
  }
  /**
   * Returns the current hue value as decimal number in the range 0..360.
   */
  get hue() {
    return this.state.currentHue * 360 / 254;
  }
  /**
   * Sets the current hue value as decimal number in the range 0..360 and converts into Matter number range.
   */
  set hue(value) {
    this.state.currentHue = cropValueRange(Math.round(value * 254 / 360), MIN_HUE_VALUE, MAX_HUE_VALUE);
  }
  /** Returns the current enhanced hue value as decimal number in the range 0..0xFFFF. */
  get enhancedHue() {
    return this.state.enhancedCurrentHue;
  }
  /**
   * Sets the current enhanced hue value as decimal number in the range 0..0xFFFF and converts into Matter number range.
   */
  set enhancedHue(value) {
    this.state.enhancedCurrentHue = cropValueRange(value, MIN_HUE_VALUE, MAX_ENHANCED_HUE_VALUE);
  }
  /** Returns the current saturation value as decimal number in the range 0..1. */
  get saturation() {
    return this.state.currentSaturation / 254;
  }
  /**
   * Sets the current saturation value as decimal number in the range 0..1 and converts into Matter number range.
   */
  set saturation(value) {
    this.state.currentSaturation = cropValueRange(
      Math.round(value * 254),
      MIN_SATURATION_VALUE,
      MAX_SATURATION_VALUE
    );
  }
  /** Returns the current color temperature value in Mireds (0..0xFFFF). */
  get mireds() {
    return this.state.colorTemperatureMireds;
  }
  /**
   * Sets the current color temperature value in Mireds (0..0xFFFF) and converts into Matter number range.
   */
  set mireds(value) {
    this.state.colorTemperatureMireds = cropValueRange(
      value,
      this.minimumColorTemperatureMireds,
      this.maximumColorTemperatureMireds
    );
  }
  /** Returns the current color temperature value in Kelvin (from internal Mireds). */
  get kelvin() {
    return miredsToKelvin(this.mireds);
  }
  /** Sets the current color temperature value in Kelvin and updates the internal mireds value. */
  set kelvin(value) {
    this.mireds = kelvinToMireds(value);
  }
  /**
   * Returns the minimum color temperature value in Mireds (0..0xFFFF). This automatically handled the case that no
   * physical minimum value is defined.
   */
  get minimumColorTemperatureMireds() {
    return this.state.colorTempPhysicalMinMireds === 0 ? MIN_TEMPERATURE_VALUE : this.state.colorTempPhysicalMinMireds;
  }
  /**
   * Returns the maximum color temperature value in Mireds (0..0xFFFF). This automatically handled the case that no
   * physical maximum value is defined.
   */
  get maximumColorTemperatureMireds() {
    return this.state.colorTempPhysicalMaxMireds === 0 ? MAX_TEMPERATURE_VALUE : this.state.colorTempPhysicalMaxMireds;
  }
  /** Returns the current whitePoint X value as decimal number according to CIE in the range 0..1. */
  get whitePointX() {
    return this.#returnAsOptionalXyValue(this.state.whitePointX);
  }
  /**
   * Sets the current whitePoint X value as decimal number according to CIE in the range 0..1 and converts into Matter
   * number range.
   */
  set whitePointX(value) {
    this.#setFromXyValue("whitePointX", value);
  }
  /** Returns the current whitePoint Y value as decimal number according to CIE in the range 0..1. */
  get whitePointY() {
    return this.#returnAsOptionalXyValue(this.state.whitePointY);
  }
  /**
   * Sets the current whitePoint Y value as decimal number according to CIE in the range 0..1 and converts into Matter
   * number range.
   */
  set whitePointY(value) {
    this.#setFromXyValue("whitePointY", value);
  }
  /** Returns the current colorPoint Rx value as decimal number according to CIE in the range 0..1. */
  get colorPointRx() {
    return this.#returnAsOptionalXyValue(this.state.colorPointRx);
  }
  /**
   * Sets the current colorPoint Rx value as decimal number according to CIE in the range 0..1 and converts into Matter
   * number range.
   */
  set colorPointRx(value) {
    this.#setFromXyValue("colorPointRx", value);
  }
  /** Returns the current colorPoint Ry value as decimal number according to CIE in the range 0..1. */
  get colorPointRy() {
    return this.#returnAsOptionalXyValue(this.state.colorPointRy);
  }
  /**
   * Sets the current colorPoint Ry value as decimal number according to CIE in the range 0..1 and converts into Matter
   * number range.
   */
  set colorPointRy(value) {
    this.#setFromXyValue("colorPointRy", value);
  }
  /** Returns the current colorPoint Gx value as decimal number according to CIE in the range 0..1. */
  get colorPointGx() {
    return this.#returnAsOptionalXyValue(this.state.colorPointGx);
  }
  /**
   * Sets the current colorPoint Gx value as decimal number according to CIE in the range 0..1 and converts into Matter
   * number range.
   */
  set colorPointGx(value) {
    this.#setFromXyValue("colorPointGx", value);
  }
  /** Returns the current colorPoint Gy value as decimal number according to CIE in the range 0..1. */
  get colorPointGy() {
    return this.#returnAsOptionalXyValue(this.state.colorPointGy);
  }
  /**
   * Sets the current colorPoint Gy value as decimal number according to CIE in the range 0..1 and converts into Matter
   * number range.
   */
  set colorPointGy(value) {
    this.#setFromXyValue("colorPointGy", value);
  }
  /** Returns the current colorPoint Bx value as decimal number according to CIE in the range 0..1. */
  get colorPointBx() {
    return this.#returnAsOptionalXyValue(this.state.colorPointBx);
  }
  /**
   * Sets the current colorPoint Bx value as decimal number according to CIE in the range 0..1 and converts into Matter
   * number range.
   */
  set colorPointBx(value) {
    this.#setFromXyValue("colorPointBx", value);
  }
  initialize() {
    this.#initializeManagedTransitionTimers();
    this.state.colorCapabilities = this.features;
    if (this.features.colorTemperature && this.#getBootReason() !== GeneralDiagnostics.BootReason.SoftwareUpdateCompleted) {
      const startUpMiredsValue = this.state.startUpColorTemperatureMireds ?? null;
      const currentMiredsValue = this.state.colorTemperatureMireds;
      let targetMiredsValue;
      switch (startUpMiredsValue) {
        case null:
          targetMiredsValue = currentMiredsValue;
          break;
        default:
          targetMiredsValue = startUpMiredsValue;
          break;
      }
      if (targetMiredsValue !== currentMiredsValue) {
        this.state.colorMode = ColorControl.ColorMode.ColorTemperatureMireds;
        this.state.enhancedColorMode = ColorControl.EnhancedColorMode.ColorTemperatureMireds;
        this.state.colorTemperatureMireds = targetMiredsValue;
      }
    }
  }
  /**
   * Default implementation notes:
   * This method ignores the transition time provided by the command and just sets the hue value to the
   * requested value. After the options and value checks and the potential color mode switch it uses the
   * {@link moveToHueLogic} method to set the hue. If you want to implement own logic just override
   * {@link moveToHueLogic} with is also used for {@link enhancedMoveToHue}, {@link moveToHueAndSaturation} and
   * {@link enhancedMoveToHueAndSaturation}.
   */
  moveToHue({
    optionsMask,
    optionsOverride,
    hue,
    direction,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.CurrentHueAndCurrentSaturation),
      () => this.moveToHueLogic(hue, direction, transitionTime)
    );
  }
  /**
   * Default implementation of the moveToHue logic. When a transition time is specified the implementation uses a
   * step based to manage the move. The method handled hue and enhanced-Hue changes depending on the last parameter.
   *
   * @param targetHue The target hue value to move to (in range of the matter hue or enhanced hue ranges)
   * @param direction The direction to move to the target hue
   * @param transitionTime The time in seconds to move to the target hue. 0 means "as fast as possible"
   * @param isEnhancedHue If true the enhanced hue is handled, otherwise the normal hue
   * @protected
   */
  moveToHueLogic(targetHue, direction, transitionTime, isEnhancedHue = false) {
    if (!this.state.managedTransitionTimeHandling || transitionTime === 0) {
      this.internal.stopHueTransition();
      if (isEnhancedHue) {
        this.state.enhancedCurrentHue = targetHue;
      } else {
        this.state.currentHue = targetHue;
      }
      this.state.remainingTime = 0;
      return;
    }
    const currentHue = isEnhancedHue ? this.state.enhancedCurrentHue : this.state.currentHue;
    const maxHue = isEnhancedHue ? MAX_ENHANCED_HUE_VALUE : MAX_HUE_VALUE;
    const effectiveRate = Math.ceil(
      this.#getHueDistanceByDirection(currentHue, targetHue, direction, maxHue) / transitionTime * 10
    );
    this.#initiateTransition(
      isEnhancedHue ? "enhancedHue" : "hue",
      effectiveRate,
      MIN_HUE_VALUE,
      maxHue,
      targetHue
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveHueLogic} method to set the hue.
   * If you want to implement own logic just override {@link moveHueLogic} with is also used for {@link enhancedMoveHue}.
   * If a currently running movement should be stopped {@link stopHueAndSaturationMovement} is used which can also be
   * overridden.
   * The logic is implemented as follows: Depending on the provided rate the server will move as fast as possible, but
   * increase only one rate directly if the managed transition is not used. Else the step logic is applied and the
   * level is increased or decreased by the step size every second.
   */
  moveHue({ optionsMask, optionsOverride, moveMode, rate }) {
    this.#assertRate(moveMode, rate);
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(this.setColorMode(ColorControl.ColorMode.CurrentHueAndCurrentSaturation), () => {
      if (moveMode === ColorControl.MoveMode.Stop) {
        return this.stopHueAndSaturationMovement();
      }
      return this.moveHueLogic(moveMode, rate);
    });
  }
  /**
   * Default implementation of the moveHue logic. The method handled hue and enhanced-Hue changes depending on the
   * last parameter.
   * If the managed transition time handling is disabled the method directly increases the hue value by one rate step.
   * So without managed transition not too much happens.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param moveMode Move Mode to move the hue up or down (Stop should not be provided in here because already handled)
   * @param rate The rate to move the hue up or down. 0 should never be provided her because handled here.
   * @param isEnhancedHue If true the enhanced hue is handled, otherwise the normal hue
   * @protected
   */
  moveHueLogic(moveMode, rate, isEnhancedHue = false) {
    const maxHue = isEnhancedHue ? MAX_ENHANCED_HUE_VALUE : MAX_HUE_VALUE;
    if (!this.state.managedTransitionTimeHandling) {
      this.internal.stopHueTransition();
      if (isEnhancedHue) {
        this.state.enhancedCurrentHue = addValueWithOverflow(
          this.state.enhancedCurrentHue,
          rate,
          MIN_HUE_VALUE,
          maxHue
        );
      } else {
        this.state.currentHue = addValueWithOverflow(this.state.currentHue, rate, MIN_HUE_VALUE, maxHue);
      }
      this.state.remainingTime = 0;
      return;
    }
    this.#initiateTransition(
      isEnhancedHue ? "enhancedHue" : "hue",
      rate * (moveMode === ColorControl.MoveMode.Up ? 1 : -1),
      MIN_HUE_VALUE,
      maxHue
    );
  }
  /**
   * Default implementation notes:
   * This method is called to stop any hue, enhanced hue and/or saturation movements potentially in progress. Override
   * this method with hardware specific logic to implement this functionality.
   * @protected
   */
  stopHueAndSaturationMovement() {
    this.internal.stopHueTransition();
    this.internal.stopSaturationTransition();
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link stepHueLogic} method to set the hue.
   * If you want to implement own logic just override {@link stepHueLogic} with is also used for {@link enhancedStepHue}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set to min/max directly. Else the step logic is applied and the hue is increased or decreased
   * by the step size every transition time interval.
   */
  stepHue({
    optionsMask,
    optionsOverride,
    stepMode,
    stepSize,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.CurrentHueAndCurrentSaturation),
      () => this.stepHueLogic(stepMode, stepSize, transitionTime)
    );
  }
  /**
   * Default implementation of the stepHue logic. The method handled hue and enhanced-Hue changes depending on the
   * last parameter.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set to current Hue +/- stepSize directly. Else the step logic is applied and the hue is increased
   * or decreased by the step size every transition time interval.
   * @param stepMode The step mode to move the hue up or down
   * @param stepSize The step size to move the hue up or down
   * @param transitionTime The time in seconds to move the hue. 0 means "as fast as possible"
   * @param isEnhancedHue If true the enhanced hue is handled, otherwise the normal hue
   * @protected
   */
  stepHueLogic(stepMode, stepSize, transitionTime, isEnhancedHue = false) {
    const currentHue = isEnhancedHue ? this.state.enhancedCurrentHue : this.state.currentHue;
    const maxHue = isEnhancedHue ? MAX_ENHANCED_HUE_VALUE : MAX_HUE_VALUE;
    const targetValue = addValueWithOverflow(
      currentHue,
      stepMode === ColorControl.StepMode.Up ? stepSize : -stepSize,
      MIN_HUE_VALUE,
      maxHue
    );
    if (!this.state.managedTransitionTimeHandling || transitionTime === 0) {
      this.internal.stopHueTransition();
      if (isEnhancedHue) {
        this.state.enhancedCurrentHue = targetValue;
      } else {
        this.state.currentHue = targetValue;
      }
      this.state.remainingTime = 0;
      return;
    }
    const effectiveRate = Math.ceil(stepSize / transitionTime * 10) * (stepMode === ColorControl.StepMode.Up ? 1 : -1);
    this.#initiateTransition(
      isEnhancedHue ? "enhancedHue" : "hue",
      effectiveRate,
      MIN_HUE_VALUE,
      maxHue,
      targetValue
    );
  }
  /**
   * Default implementation notes:
   * After the options checks and potentially switching the color mode it uses the {@link moveToSaturationLogic}
   * method to set the hue.
   * If you want to implement own logic just override {@link moveToSaturationLogic} with is also used for
   * {@link moveToHueAndSaturation}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target value directly. Else the step logic is applied and the saturation is increased or
   * decreased by the step size every transition time interval.
   */
  moveToSaturation({
    optionsMask,
    optionsOverride,
    saturation,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.CurrentHueAndCurrentSaturation),
      () => this.moveToSaturationLogic(saturation, transitionTime)
    );
  }
  /**
   * Default implementation of the moveToSaturation logic.
   * If the managed transition time handling is disabled the method directly sets the new saturation value.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param targetSaturation The target saturation value to move to
   * @param transitionTime The time in seconds to move to the target saturation. 0 means "as fast as possible"
   * @protected
   */
  moveToSaturationLogic(targetSaturation, transitionTime) {
    if (!this.state.managedTransitionTimeHandling || transitionTime === 0) {
      this.internal.stopSaturationTransition();
      this.state.currentSaturation = targetSaturation;
      this.state.remainingTime = 0;
      return;
    }
    const effectiveRate = Math.ceil((targetSaturation - this.state.currentSaturation) / transitionTime * 10);
    this.#initiateTransition(
      "saturation",
      effectiveRate,
      MIN_SATURATION_VALUE,
      MAX_SATURATION_VALUE,
      targetSaturation
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveSaturationLogic} method to set the hue.
   * If you want to implement own logic just override {@link moveSaturationLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set to min/max directly. Else the step logic is applied and the saturation is increased or
   * decreased by the step size every transition time interval.
   */
  moveSaturation({ optionsMask, optionsOverride, moveMode, rate }) {
    this.#assertRate(moveMode, rate);
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(this.setColorMode(ColorControl.ColorMode.CurrentHueAndCurrentSaturation), () => {
      if (moveMode === ColorControl.MoveMode.Stop) {
        return this.stopHueAndSaturationMovement();
      }
      return this.moveSaturationLogic(moveMode, rate);
    });
  }
  /**
   * Default implementation of the moveSaturation logic.
   * If the managed transition time handling is disabled the method directly sets the new saturation value.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param moveMode Move Mode to move the saturation up or down
   * @param rate The rate to move the saturation up or down
   * @protected
   */
  moveSaturationLogic(moveMode, rate) {
    if (!this.state.managedTransitionTimeHandling) {
      this.internal.stopSaturationTransition();
      this.state.currentSaturation = moveMode === ColorControl.MoveMode.Up ? MAX_SATURATION_VALUE : MIN_SATURATION_VALUE;
      this.state.remainingTime = 0;
      return;
    }
    this.#initiateTransition(
      "saturation",
      rate * (moveMode === ColorControl.MoveMode.Up ? 1 : -1),
      MIN_SATURATION_VALUE,
      MAX_SATURATION_VALUE
    );
  }
  /**
   * Default implementation notes:
   * After the options checks and potentially switching the color mode it uses the {@link stepSaturationLogic}
   * method to set the hue.
   * If you want to implement own logic just override {@link stepSaturationLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target values directly. Else the step logic is applied and the hue and saturation are
   * increased or decreased by the step size every transition time interval.
   */
  stepSaturation({
    optionsMask,
    optionsOverride,
    stepMode,
    stepSize,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.CurrentHueAndCurrentSaturation),
      () => this.stepSaturationLogic(stepMode, stepSize, transitionTime)
    );
  }
  /**
   * Default implementation of the stepSaturation logic.
   * If the managed transition time handling is disabled the method directly sets the new saturation value.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param stepMode The step mode to move the saturation up or down
   * @param stepSize The step size to move the saturation up or down
   * @param transitionTime The time in seconds to move the saturation. 0 means "as fast as possible"
   * @protected
   */
  stepSaturationLogic(stepMode, stepSize, transitionTime) {
    const effectiveRate = Math.ceil(stepSize / transitionTime * 10) * (stepMode === ColorControl.StepMode.Up ? 1 : -1);
    const targetValue = cropValueRange(
      stepMode === ColorControl.StepMode.Up ? this.state.currentSaturation + stepSize : this.state.currentSaturation - stepSize,
      MIN_SATURATION_VALUE,
      MAX_SATURATION_VALUE
    );
    if (!this.state.managedTransitionTimeHandling || transitionTime === 0 || this.state.currentSaturation === targetValue) {
      this.internal.stopSaturationTransition();
      this.state.currentSaturation = targetValue;
      this.state.remainingTime = 0;
      return;
    }
    this.#initiateTransition("saturation", effectiveRate, MIN_SATURATION_VALUE, MAX_SATURATION_VALUE, targetValue);
  }
  /**
   * Default implementation notes:
   * After the options checks and potentially switching the color mode it uses the {@link moveToHueAndSaturationLogic}
   * method to set the hue.
   * If you want to implement own logic just override {@link moveToHueAndSaturationLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target values directly. Else the step logic is applied and the hue and saturation is
   * increased or decreased by the step size every transition time interval.
   */
  moveToHueAndSaturation({
    optionsOverride,
    optionsMask,
    saturation,
    hue,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.CurrentHueAndCurrentSaturation),
      () => this.moveToHueAndSaturationLogic(hue, saturation, transitionTime)
    );
  }
  /**
   * Default implementation of the moveToHueAndSaturation logic.
   * If the managed transition time handling is disabled the method directly sets the new hue and saturation values.
   * Otherwise the method initiates a transition with the given rate.
   * This method internally uses {@link moveToHueLogic} and {@link moveToSaturationLogic} to handle the hue and
   * saturation changes, so if you have implemented them already you might not need to override this method.
   *
   * @param targetHue The target hue value to move to
   * @param targetSaturation The target saturation value to move to
   * @param transitionTime The time in seconds to move to the target hue and saturation. 0 means "as fast as possible"
   * @protected
   */
  moveToHueAndSaturationLogic(targetHue, targetSaturation, transitionTime) {
    return MaybePromise.then(
      this.moveToHueLogic(targetHue, ColorControl.Direction.ShortestDistance, transitionTime, false),
      () => this.moveToSaturationLogic(targetSaturation, transitionTime)
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveToColorLogic} method to set the x and y value.
   * If you want to implement own logic just override {@link moveToColorLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target values directly. Else the step logic is applied and the x and y is
   * increased or decreased by the step size every transition time interval.
   */
  moveToColor({
    optionsMask,
    optionsOverride,
    colorX,
    colorY,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.CurrentXAndCurrentY),
      () => this.moveToColorLogic(colorX, colorY, transitionTime)
    );
  }
  /**
   * Default implementation of the moveToColor logic.
   * If the managed transition time handling is disabled the method directly sets the new x and y values.
   * Otherwise the method initiates a transition with the given rate.
   * This method internally uses {@link moveToColorLogic} to handle the x and y changes, so if you have implemented it
   * already you might not need to override this method.
   *
   * @param targetX The target x value to move to
   * @param targetY The target y value to move to
   * @param transitionTime The time in seconds to move to the target x and y. 0 means "as fast as possible"
   * @protected
   */
  moveToColorLogic(targetX, targetY, transitionTime) {
    if (!this.state.managedTransitionTimeHandling || transitionTime === 0) {
      this.internal.stopXTransition();
      this.internal.stopYTransition();
      this.state.currentX = cropValueRange(targetX, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE);
      this.state.currentY = cropValueRange(targetY, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE);
      this.state.remainingTime = 0;
      return;
    }
    const effectiveRateX = Math.floor((targetX - this.state.currentX) / transitionTime * 10);
    const effectiveRateY = Math.floor((targetY - this.state.currentY) / transitionTime * 10);
    this.#initiateTransition("x", effectiveRateX, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE, targetX);
    this.#initiateTransition("y", effectiveRateY, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE, targetY);
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveColorLogic} method to set the x and y value.
   * If you want to implement own logic just override {@link moveColorLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target values directly. Else the step logic is applied and the x and y is
   * increased or decreased by the step size every transition time interval.
   */
  moveColor({ optionsOverride, optionsMask, rateX, rateY }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    if (rateX === 0 && rateY === 0) {
      return this.stopAllColorMovement();
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.CurrentXAndCurrentY),
      () => this.moveColorLogic(rateX, rateY)
    );
  }
  /**
   * Default implementation notes:
   * This method is called to stop any color movements potentially in progress, including any x/y, hue, saturation and
   * color temperature transitions. Override this method with hardware specific logic to implement this functionality.
   * @protected
   */
  stopAllColorMovement() {
    this.internal.stopXTransition();
    this.internal.stopYTransition();
    this.internal.stopHueTransition();
    this.internal.stopSaturationTransition();
    this.internal.stopColorTemperatureTransition();
    this.state.remainingTime = 0;
  }
  /**
   * Default implementation of the moveColor logic.
   * If the managed transition time handling is disabled the method directly sets the new x and y values.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param rateX The rate to move the x value up or down (positive values mean up, negative down)
   * @param rateY The rate to move the y value up or down (positive values mean up, negative down)
   * @protected
   */
  moveColorLogic(rateX, rateY) {
    if (!this.state.managedTransitionTimeHandling) {
      this.internal.stopXTransition();
      this.internal.stopYTransition();
      if (rateX !== 0) {
        this.state.currentX = MAX_CIE_XY_VALUE;
      }
      if (rateY !== 0) {
        this.state.currentY = MAX_CIE_XY_VALUE;
      }
      this.state.remainingTime = 0;
      return;
    }
    if (rateX !== 0) {
      this.#initiateTransition("x", rateX, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE);
    }
    if (rateY !== 0) {
      this.#initiateTransition("y", rateY, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE);
    }
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link stepColorLogic} method to set the x and y value.
   * If you want to implement own logic just override {@link stepColorLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target values directly. Else the step logic is applied and the x and y is
   * increased or decreased by the step size every transition time interval.
   */
  stepColor({
    optionsOverride,
    optionsMask,
    stepX,
    stepY,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.CurrentXAndCurrentY),
      () => this.stepColorLogic(stepX, stepY, transitionTime)
    );
  }
  /**
   * Default implementation of the stepColor logic.
   * If the managed transition time handling is disabled the method directly sets the new x and y values.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param stepX The step size to move the x value up or down
   * @param stepY The step size to move the y value up or down
   * @param transitionTime The time in seconds to move the x and y. 0 means "as fast as possible"
   * @protected
   */
  stepColorLogic(stepX, stepY, transitionTime) {
    const targetValueX = cropValueRange(this.state.currentX + stepX, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE);
    const targetValueY = cropValueRange(this.state.currentY + stepY, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE);
    if (!this.state.managedTransitionTimeHandling || transitionTime === 0) {
      this.internal.stopXTransition();
      this.internal.stopYTransition();
      this.state.currentX = targetValueX;
      this.state.currentY = targetValueY;
      this.state.remainingTime = 0;
      return;
    }
    if (stepX !== 0) {
      const effectiveRateX = Math.floor(stepX / transitionTime * 10);
      this.#initiateTransition("x", effectiveRateX, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE, targetValueX);
    }
    if (stepY !== 0) {
      const effectiveRateY = Math.floor(stepY / transitionTime * 10);
      this.#initiateTransition("y", effectiveRateY, MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE, targetValueY);
    }
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveToColorTemperatureLogic} method to set the color temperature.
   * If you want to implement own logic just override {@link moveToColorTemperatureLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target value directly. Else the step logic is applied and the color temperature is
   * increased or decreased by the step size every transition time interval.
   */
  moveToColorTemperature({
    optionsOverride,
    optionsMask,
    colorTemperatureMireds,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setColorMode(ColorControl.ColorMode.ColorTemperatureMireds),
      () => this.moveToColorTemperatureLogic(colorTemperatureMireds, transitionTime)
    );
  }
  /**
   * Default implementation of the moveToColorTemperature logic.
   * If the managed transition time handling is disabled the method directly sets the new color temperature value.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param targetMireds The target color temperature value to move to
   * @param transitionTime The time in seconds to move to the target color temperature. 0 means "as fast as possible"
   * @protected
   */
  moveToColorTemperatureLogic(targetMireds, transitionTime) {
    if (!this.state.managedTransitionTimeHandling || transitionTime === 0) {
      this.internal.stopColorTemperatureTransition();
      this.state.colorTemperatureMireds = targetMireds;
      this.state.remainingTime = 0;
      return;
    }
    const effectiveRate = Math.ceil((targetMireds - this.state.colorTemperatureMireds) / transitionTime * 10);
    this.#initiateTransition(
      "colorTemperature",
      effectiveRate,
      this.minimumColorTemperatureMireds,
      this.maximumColorTemperatureMireds,
      targetMireds
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveToHueLogic} method to set the enhanced hue.
   * If you want to implement own logic just override {@link moveToHueLogic} which is used together with
   * {@link moveToHue}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target value directly. Else the step logic is applied and the color temperature is
   * increased or decreased by the step size every transition time interval.
   */
  enhancedMoveToHue({
    optionsMask,
    optionsOverride,
    enhancedHue,
    direction,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setEnhancedColorMode(ColorControl.EnhancedColorMode.EnhancedCurrentHueAndCurrentSaturation),
      () => this.moveToHueLogic(enhancedHue, direction, transitionTime, true)
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveHueLogic} method to set the enhanced hue.
   * If you want to implement own logic just override {@link moveHueLogic} which is used together with
   * {@link moveHue}.
   * The logic is implemented as follows: Depending on the provided rate the server will move as fast as possible, but
   * increase only one rate directly if the managed transition is not used. Else the step logic is applied and the
   * level is increased or decreased by the step size every second.
   */
  enhancedMoveHue({ optionsMask, optionsOverride, moveMode, rate }) {
    this.#assertRate(moveMode, rate);
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setEnhancedColorMode(ColorControl.EnhancedColorMode.EnhancedCurrentHueAndCurrentSaturation),
      () => {
        if (moveMode === ColorControl.MoveMode.Stop) {
          return this.stopHueAndSaturationMovement();
        }
        return this.moveHueLogic(moveMode, rate, true);
      }
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link stepHueLogic} method to set the enhanced hue.
   * If you want to implement own logic just override {@link stepHueLogic} which is used together with
   * {@link stepHue}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target value directly. Else the step logic is applied and the hue is increased or
   * decreased by the step size every transition time interval.
   */
  enhancedStepHue({
    optionsMask,
    optionsOverride,
    stepMode,
    stepSize,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setEnhancedColorMode(ColorControl.EnhancedColorMode.EnhancedCurrentHueAndCurrentSaturation),
      () => this.stepHueLogic(stepMode, stepSize, transitionTime, true)
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveToEnhancedHueAndSaturationLogic} method to set the enhanced hue
   * and saturation.
   * If you want to implement own logic just override {@link moveToEnhancedHueAndSaturationLogic}.
   * The logic is implemented as follows: Depending on the provided rate the server will move as fast as possible, so
   * we set to min/max directly if the managed transition is not used. Else the step logic is applied and the
   * level is increased or decreased by the step size every second.
   */
  enhancedMoveToHueAndSaturation({
    optionsOverride,
    optionsMask,
    enhancedHue,
    saturation,
    transitionTime
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(
      this.setEnhancedColorMode(ColorControl.EnhancedColorMode.EnhancedCurrentHueAndCurrentSaturation),
      () => this.moveToEnhancedHueAndSaturationLogic(enhancedHue, saturation, transitionTime)
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link moveToHueLogic} and {@link moveToSaturationLogic} methods to set the
   * hue and saturation.
   * Both methods are also used for other commands, so it maybe is not needed to override this method.
   * The logic is implemented as follows: Depending on the provided rate the server will move as fast as possible, so
   * we set to min/max directly if the managed transition is not used. Else the step logic is applied and the
   * level is increased or decreased by the step size every second.
   */
  moveToEnhancedHueAndSaturationLogic(targetEnhancedHue, targetSaturation, transitionTime) {
    return MaybePromise.then(
      this.moveToHueLogic(targetEnhancedHue, ColorControl.Direction.ShortestDistance, transitionTime, true),
      () => this.moveToSaturationLogic(targetSaturation, transitionTime)
    );
  }
  /**
   * Default implementation notes:
   * After the options checks and potentially switching the color mode it uses the {@link stopColorLoopLogic}
   * method to stop color loops or {@link startColorLoopLogic} to start a color loop at a defined enhanced hue value.
   * If you want to implement own logic just override these methods.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target value directly. Else the step logic is applied and the color temperature is
   * increased or decreased by the step size every transition time interval.
   */
  colorLoopSet({
    optionsMask,
    optionsOverride,
    updateFlags,
    action,
    direction,
    time,
    startHue
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    if (updateFlags.updateDirection) {
      this.state.colorLoopDirection = direction;
    }
    if (updateFlags.updateTime) {
      this.state.colorLoopTime = time;
    }
    if (updateFlags.updateStartHue) {
      this.state.colorLoopStartEnhancedHue = startHue;
    }
    if (updateFlags.updateAction) {
      if (action === ColorControl.Action.DeActivateTheColorLoop) {
        return this.#stopColorLoop();
      } else {
        return MaybePromise.then(
          this.setEnhancedColorMode(ColorControl.EnhancedColorMode.EnhancedCurrentHueAndCurrentSaturation),
          () => {
            this.state.colorLoopStoredEnhancedHue = this.state.enhancedCurrentHue;
            this.state.colorLoopActive = ColorControl.ColorLoopActive.Active;
            if (action === ColorControl.Action.ActivateTheColorLoopFromTheValueInTheColorLoopStartEnhancedHueField) {
              return this.startColorLoopLogic(this.state.colorLoopStartEnhancedHue);
            } else if (action === ColorControl.Action.ActivateTheColorLoopFromTheValueOfTheEnhancedCurrentHueAttribute) {
              return this.startColorLoopLogic(this.state.enhancedCurrentHue);
            }
          }
        );
      }
    }
  }
  /**
   * Internal method to step the color loop and sync the internal ColorLoopActive state and restore the stored
   * enhanced hue. It uses {@link stopColorLoopLogic} to execute the stop.
   */
  #stopColorLoop() {
    return MaybePromise.then(this.stopColorLoopLogic(), () => {
      if (this.state.colorLoopActive === ColorControl.ColorLoopActive.Active) {
        this.state.colorLoopActive = ColorControl.ColorLoopActive.Inactive;
        this.state.enhancedCurrentHue = this.state.colorLoopStoredEnhancedHue;
      }
    });
  }
  /**
   * Default implementation notes:
   * Implement stopping the color loop here if any special hardware specific logic is needed.
   * @protected
   */
  stopColorLoopLogic() {
    this.internal.stopHueTransition();
  }
  /**
   * Default implementation notes:
   * This method uses {@link moveHueLogic} to start a enhanced color loop and sets the required start hue beforehand.
   * If this is sufficient for your implementation you might not need to override this method.
   *
   * @param startHue The hue to start the color loop at
   * @protected
   */
  startColorLoopLogic(startHue) {
    this.state.enhancedCurrentHue = startHue;
    return this.moveHueLogic(
      this.state.colorLoopDirection === ColorControl.ColorLoopDirection.Decrement ? ColorControl.MoveMode.Down : ColorControl.MoveMode.Up,
      Math.floor(MAX_ENHANCED_HUE_VALUE / this.state.colorLoopTime),
      true
    );
  }
  /**
   * Default implementation notes:
   * After the options checks it uses the {@link stopMoveStepLogic} method to stop any ongoing color movement.
   * If you want to implement own logic just override {@link stopMoveStepLogic}.
   */
  stopMoveStep({ optionsOverride, optionsMask }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return this.stopMoveStepLogic();
  }
  /**
   * Default implementation notes:
   * Implement stopping any ongoing color movement here if any special hardware specific logic is needed. In fact this
   * needs to stop any ongoing saturation, color temperature, x or y movement. Hue transitions should only be stopped
   * when they do not belong to a color loop.
   * @protected
   */
  stopMoveStepLogic() {
    if (this.state.colorLoopActive === ColorControl.ColorLoopActive.Inactive) {
      this.internal.stopHueTransition();
    }
    this.internal.stopSaturationTransition();
    this.internal.stopColorTemperatureTransition();
    this.internal.stopXTransition();
    this.internal.stopYTransition();
  }
  /**
   * Default implementation notes:
   * After the options checks and potentially changing the color mode it uses the {@link moveColorTemperatureLogic}
   * method to set the color temperature.
   * If the current movement should be stopped {@link stopHueAndSaturationMovement} is used.
   * If you want to implement own logic just override {@link moveColorTemperatureLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target value directly. Else the step logic is applied and the color temperature is
   * increased or decreased by the step size every transition time interval.
   */
  moveColorTemperature({
    optionsOverride,
    optionsMask,
    moveMode,
    colorTemperatureMinimumMireds,
    colorTemperatureMaximumMireds,
    rate
  }) {
    this.#assertRate(moveMode, rate);
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(this.setColorMode(ColorControl.ColorMode.ColorTemperatureMireds), () => {
      if (moveMode === ColorControl.MoveMode.Stop) {
        return this.stopHueAndSaturationMovement();
      }
      if (colorTemperatureMinimumMireds === 0) {
        colorTemperatureMinimumMireds = this.minimumColorTemperatureMireds;
      }
      if (colorTemperatureMaximumMireds === 0) {
        colorTemperatureMaximumMireds = this.maximumColorTemperatureMireds;
      }
      colorTemperatureMinimumMireds = cropValueRange(
        colorTemperatureMinimumMireds,
        this.minimumColorTemperatureMireds,
        this.maximumColorTemperatureMireds
      );
      colorTemperatureMaximumMireds = cropValueRange(
        colorTemperatureMaximumMireds,
        this.minimumColorTemperatureMireds,
        this.maximumColorTemperatureMireds
      );
      return this.moveColorTemperatureLogic(
        moveMode,
        rate,
        colorTemperatureMinimumMireds,
        colorTemperatureMaximumMireds
      );
    });
  }
  /**
   * Default implementation of the moveColorTemperature logic.
   * If the managed transition time handling is disabled the method directly sets the min or max color temperature value.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param moveMode
   * @param rate
   * @param colorTemperatureMinimumMireds
   * @param colorTemperatureMaximumMireds
   * @protected
   */
  moveColorTemperatureLogic(moveMode, rate, colorTemperatureMinimumMireds, colorTemperatureMaximumMireds) {
    if (!this.state.managedTransitionTimeHandling) {
      this.internal.stopColorTemperatureTransition();
      this.state.colorTemperatureMireds = moveMode === ColorControl.MoveMode.Up ? colorTemperatureMaximumMireds : colorTemperatureMinimumMireds;
      this.state.remainingTime = 0;
      return;
    }
    const effectiveRate = rate * (moveMode === ColorControl.MoveMode.Up ? 1 : -1);
    this.#initiateTransition(
      "colorTemperature",
      effectiveRate,
      colorTemperatureMinimumMireds,
      colorTemperatureMaximumMireds
    );
  }
  /**
   * Default implementation notes:
   * After the options checks and potentially switching the color mode it uses the {@link stepColorTemperatureLogic}
   * method to set the color temperature.
   * If you want to implement own logic just override {@link stepColorTemperatureLogic}.
   * The logic is implemented as follows: When no transition time is provided, the server will move as fast as
   * possible, so we set the target value directly. Else the step logic is applied and the color temperature is
   * increased or decreased by the step size every transition time interval.
   */
  stepColorTemperature({
    optionsOverride,
    optionsMask,
    stepMode,
    stepSize,
    transitionTime,
    colorTemperatureMinimumMireds,
    colorTemperatureMaximumMireds
  }) {
    if (!this.#optionsAllowExecution(optionsMask, optionsOverride)) {
      return;
    }
    return MaybePromise.then(this.setColorMode(ColorControl.ColorMode.ColorTemperatureMireds), () => {
      if (colorTemperatureMinimumMireds === 0) {
        colorTemperatureMinimumMireds = this.minimumColorTemperatureMireds;
      }
      if (colorTemperatureMaximumMireds === 0) {
        colorTemperatureMaximumMireds = this.maximumColorTemperatureMireds;
      }
      colorTemperatureMinimumMireds = cropValueRange(
        colorTemperatureMinimumMireds,
        this.minimumColorTemperatureMireds,
        this.maximumColorTemperatureMireds
      );
      colorTemperatureMaximumMireds = cropValueRange(
        colorTemperatureMaximumMireds,
        this.minimumColorTemperatureMireds,
        this.maximumColorTemperatureMireds
      );
      return this.stepColorTemperatureLogic(
        stepMode,
        stepSize,
        transitionTime,
        colorTemperatureMinimumMireds,
        colorTemperatureMaximumMireds
      );
    });
  }
  /**
   * Default implementation of the stepColorTemperature logic.
   * If the managed transition time handling is disabled the method directly sets the new color temperature value.
   * Otherwise the method initiates a transition with the given rate.
   *
   * @param stepMode Step Mode to move the color temperature up or down
   * @param stepSize Step size to move the color temperature up or down
   * @param transitionTime Time in seconds to move the color temperature. 0 means "as fast as possible"
   * @param colorTemperatureMinimumMireds Minimum allowed color temperature value
   * @param colorTemperatureMaximumMireds Maximum allowed color temperature value
   * @protected
   */
  stepColorTemperatureLogic(stepMode, stepSize, transitionTime, colorTemperatureMinimumMireds, colorTemperatureMaximumMireds) {
    const targetValue = cropValueRange(
      this.state.colorTemperatureMireds + (stepMode === ColorControl.StepMode.Up ? stepSize : -stepSize),
      colorTemperatureMinimumMireds,
      colorTemperatureMaximumMireds
    );
    if (!this.state.managedTransitionTimeHandling || transitionTime === 0) {
      this.internal.stopColorTemperatureTransition();
      this.state.colorTemperatureMireds = targetValue;
      this.state.remainingTime = 0;
      return;
    }
    const effectiveRate = Math.ceil(stepSize / transitionTime * 10) * (stepMode === ColorControl.StepMode.Up ? 1 : -1);
    this.#initiateTransition(
      "colorTemperature",
      effectiveRate,
      colorTemperatureMinimumMireds,
      colorTemperatureMaximumMireds
    );
  }
  /**
   * Convenient method to change the color mode of the device. It uses {@link switchColorMode} to
   * convert the current color attributes to the new mode. Override this method for device specific behavior.
   * @private
   */
  setColorMode(mode) {
    if (this.state.colorMode === mode) {
      return;
    }
    return MaybePromise.then(this.switchColorMode(this.state.colorMode, mode), () => {
      this.state.colorMode = mode;
      this.state.enhancedColorMode = mode;
    });
  }
  /**
   * Convenient method to change the enhanced color mode of the device. it uses {@link switchColorMode} to
   * convert the current color attributes to the new mode. Override this method for device specific behavior.
   * @private
   */
  setEnhancedColorMode(mode) {
    if (this.state.enhancedColorMode === mode) {
      return;
    }
    let colorModeSet;
    if (mode === ColorControl.EnhancedColorMode.EnhancedCurrentHueAndCurrentSaturation) {
      colorModeSet = this.setColorMode(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
    } else {
      colorModeSet = this.setColorMode(mode);
    }
    return MaybePromise.then(colorModeSet, () => {
      this.state.enhancedColorMode = mode;
    });
  }
  /**
   * Default implementation notes:
   * This method is used to switch the device color mode. It converts the current color attributes to the new mode on
   * a best effort "as close as possible" basis. If the device hardware can do this better this method should be
   * overridden to implement this switch logic and to update all state attributes for the new mode. The color mode
   * attribute update itself is handled by the calling method, so is not needed to be done here.
   *
   * @param oldMode Old color Mode
   * @param newMode New color Mode
   * @protected
   */
  switchColorMode(oldMode, newMode) {
    if (oldMode === newMode) {
      return;
    }
    MaybePromise.then(this.stopAllColorMovement(), () => {
      switch (oldMode) {
        case ColorControl.ColorMode.CurrentHueAndCurrentSaturation:
          switch (newMode) {
            case ColorControl.ColorMode.CurrentXAndCurrentY:
              const [x, y] = hsvToXy(this.hue, this.saturation);
              this.x = x;
              this.y = y;
              break;
            case ColorControl.ColorMode.ColorTemperatureMireds:
              const mireds = hsvToMireds(this.hue, this.saturation);
              if (mireds === void 0) {
                logger.warn(
                  `Could not convert hue/saturation (${this.hue}/${this.saturation}) to color temperature`
                );
              } else {
                this.mireds = mireds;
              }
              break;
          }
          break;
        case ColorControl.ColorMode.CurrentXAndCurrentY:
          switch (newMode) {
            case ColorControl.ColorMode.CurrentHueAndCurrentSaturation:
              const [hue, saturation] = hsvToXy(this.x, this.y);
              this.hue = hue;
              this.saturation = saturation;
              break;
            case ColorControl.ColorMode.ColorTemperatureMireds:
              const mireds = xyToMireds(this.x, this.y);
              if (mireds === void 0) {
                logger.warn(`Could not convert xy ${this.x / this.y} to color temperature`);
              } else {
                this.mireds = mireds;
              }
              break;
          }
          break;
        case ColorControl.ColorMode.ColorTemperatureMireds:
          switch (newMode) {
            case ColorControl.ColorMode.CurrentHueAndCurrentSaturation:
              const hsvResult = miredsToHsv(this.mireds);
              if (hsvResult === void 0) {
                logger.warn(`Could not convert color temperature ${this.mireds} to hue/saturation`);
              } else {
                const [hue, saturation] = hsvResult;
                this.hue = hue;
                this.saturation = saturation;
              }
              break;
            case ColorControl.ColorMode.CurrentXAndCurrentY:
              const xyResult = miredsToXy(this.mireds);
              if (xyResult === void 0) {
                logger.warn("Could not convert color temperature to xy");
              } else {
                const [x, y] = xyResult;
                this.x = x;
                this.y = y;
              }
              break;
          }
          break;
      }
    });
  }
  /**
   * This method is used to set the remaining Time by checking the current transition timers depending on the color mode.
   */
  #setRemainingTime() {
    let remaining = 0;
    switch (this.state.colorMode) {
      case ColorControl.ColorMode.CurrentHueAndCurrentSaturation:
        if (this.internal.hueTransitionIntervalTimer?.isRunning) {
          remaining = this.internal.currentHueTransitionData?.remainingTime ?? 0;
        }
        if (this.internal.saturationTransitionIntervalTimer?.isRunning) {
          remaining = Math.max(remaining, this.internal.currentSaturationTransitionData?.remainingTime ?? 0);
        }
        break;
      case ColorControl.ColorMode.CurrentXAndCurrentY:
        if (this.internal.xTransitionIntervalTimer?.isRunning) {
          remaining = this.internal.currentXTransitionData?.remainingTime ?? 0;
        }
        if (this.internal.yTransitionIntervalTimer?.isRunning) {
          remaining = Math.max(remaining, this.internal.currentYTransitionData?.remainingTime ?? 0);
        }
        break;
      case ColorControl.ColorMode.ColorTemperatureMireds:
        if (this.internal.colorTemperatureTransitionIntervalTimer?.isRunning) {
          remaining = this.internal.currentColorTemperatureTransitionData?.remainingTime ?? 0;
        }
        break;
    }
    this.state.remainingTime = remaining;
  }
  /**
   * This method is used internally to potentially handle the dependency between the ColorControl and LevelControl
   * cluster.
   * Do not override this method! Please use the {@link syncColorTemperatureWithLevelLogic} method instead which is
   * called by this method if a sync is needed.
   */
  syncColorTemperatureWithLevel(level) {
    if (this.state.colorMode === ColorControl.ColorMode.ColorTemperatureMireds || this.state.enhancedColorMode === ColorControl.EnhancedColorMode.ColorTemperatureMireds) {
      return this.syncColorTemperatureWithLevelLogic(level);
    }
  }
  /**
   * This method is used internally to potentially handle the dependency between the ColorControl and LevelControl
   * clusters as defined by the Matter specification. It uses {@link moveToColorTemperatureLogic} with a transition
   * time of 0 (as fast as possible) to set the determined color temperature. Unless you have special requirements
   * you do not need to override it.
   *
   * @param level The current level value from the LevelControl cluster
   * @protected
   */
  syncColorTemperatureWithLevelLogic(level) {
    if (this.state.colorMode !== ColorControl.ColorMode.ColorTemperatureMireds && this.state.enhancedColorMode !== ColorControl.EnhancedColorMode.ColorTemperatureMireds) {
      return;
    }
    const tempCoupleMin = this.state.coupleColorTempToLevelMinMireds ?? this.minimumColorTemperatureMireds;
    const tempPhysMax = this.maximumColorTemperatureMireds;
    let newColorTemp;
    if (level <= MIN_CURRENT_LEVEL) {
      newColorTemp = tempPhysMax;
    } else if (level >= MAX_CURRENT_LEVEL) {
      newColorTemp = tempCoupleMin;
    } else {
      const tempDelta = Math.floor(
        (tempPhysMax - tempCoupleMin) * level / (MAX_CURRENT_LEVEL - MIN_CURRENT_LEVEL + 1)
      );
      newColorTemp = tempPhysMax - tempDelta;
    }
    logger.debug(`Synced color temperature with level: ${level}, new color temperature: ${newColorTemp}`);
    return this.moveToColorTemperatureLogic(newColorTemp, 0);
  }
  #assertRate(mode, rate) {
    if ((mode === ColorControl.MoveMode.Up || mode === ColorControl.MoveMode.Down) && rate === 0) {
      throw new StatusResponseError("Rate must not be 0 when moving Up or Down", StatusCode.InvalidCommand);
    }
  }
  /** Calculate the hue distance depending on the direction and the current and target hue. */
  #getHueDistanceByDirection(currentHue, targetHue, direction, max) {
    const distance = (targetHue > currentHue ? targetHue : max + targetHue) - currentHue;
    logger.info(
      `Distance: ${distance}, direction: ${direction}, max/2: ${max / 2}, max-distance: ${max - distance}`
    );
    if (distance === 0) {
      return 0;
    }
    if (direction === ColorControl.Direction.Up) {
      return distance;
    } else if (direction === ColorControl.Direction.Down) {
      return -(max - distance);
    }
    if (direction === ColorControl.Direction.ShortestDistance) {
      if (Math.abs(distance) > max / 2) {
        return -(max - distance);
      }
      return distance;
    }
    if (direction === ColorControl.Direction.LongestDistance) {
      if (Math.abs(distance) > max / 2) {
        return distance;
      }
      return -(max - distance);
    }
    throw new ImplementationError(`Unknown direction: ${direction}`);
  }
  /** Initialize the managed transition timers.. */
  #initializeManagedTransitionTimers() {
    if (!this.state.managedTransitionTimeHandling) {
      return;
    }
    this.internal.hueTransitionIntervalTimer = Time.getPeriodicTimer(
      "ColorControl.hue.transition",
      1e3,
      this.callback(this.#hueStepIntervalTick, { lock: true })
    );
    this.internal.saturationTransitionIntervalTimer = Time.getPeriodicTimer(
      "ColorControl.saturation.transition",
      1e3,
      this.callback(this.#saturationStepIntervalTick, { lock: true })
    );
    this.internal.colorTemperatureTransitionIntervalTimer = Time.getPeriodicTimer(
      "ColorControl.colorTemperature.transition",
      1e3,
      this.callback(this.#colorTemperatureStepIntervalTick, { lock: true })
    );
    this.internal.xTransitionIntervalTimer = Time.getPeriodicTimer(
      "ColorControl.x.transition",
      1e3,
      this.callback(this.#xStepIntervalTick, { lock: true })
    );
    this.internal.yTransitionIntervalTimer = Time.getPeriodicTimer(
      "ColorControl.y.transition",
      1e3,
      this.callback(this.#yStepIntervalTick, { lock: true })
    );
  }
  #getBootReason() {
    const rootEndpoint = this.endpoint.ownerOfType(RootEndpoint);
    if (rootEndpoint !== void 0 && rootEndpoint.behaviors.has(GeneralDiagnosticsBehavior)) {
      return rootEndpoint.stateOf(GeneralDiagnosticsBehavior).bootReason;
    }
  }
  #calculateEffectiveOptions(optionsMask, optionsOverride) {
    const options = this.state.options ?? {};
    return {
      executeIfOff: optionsMask.executeIfOff ? optionsOverride.executeIfOff : options.executeIfOff
    };
  }
  #optionsAllowExecution(optionsMask, optionsOverride) {
    const options = this.#calculateEffectiveOptions(optionsMask, optionsOverride);
    return options.executeIfOff || !this.agent.has(OnOffServer) || this.agent.get(OnOffServer).state.onOff;
  }
  #initiateTransition(transitionType, changeRate, minValue, maxValue, targetValue) {
    if (changeRate === 0) {
      logger.warn(`Change rate for ${transitionType} is 0, nothing to do.`);
    }
    let currentValue;
    switch (transitionType) {
      case "hue":
        currentValue = this.state.currentHue;
        break;
      case "enhancedHue":
        currentValue = this.state.enhancedCurrentHue;
        break;
      case "saturation":
        currentValue = this.state.currentSaturation;
        break;
      case "colorTemperature":
        currentValue = this.state.colorTemperatureMireds;
        break;
      case "x":
        currentValue = this.state.currentX;
        break;
      case "y":
        currentValue = this.state.currentY;
        break;
    }
    let hueDistanceLeft = void 0;
    let remainingTime = 65535;
    if (transitionType === "enhancedHue" || transitionType === "hue") {
      if (targetValue !== void 0) {
        const direction = changeRate > 0 ? ColorControl.Direction.Up : ColorControl.Direction.Down;
        hueDistanceLeft = this.#getHueDistanceByDirection(currentValue, targetValue, direction, maxValue);
        remainingTime = Math.floor(Math.ceil(hueDistanceLeft / changeRate) * 10);
      }
    } else {
      if (changeRate > 0) {
        remainingTime = Math.floor(Math.ceil((maxValue - currentValue) / changeRate) * 10);
      } else {
        remainingTime = Math.floor(Math.ceil((currentValue - minValue) / -changeRate) * 10);
      }
    }
    const transitionData = {
      changeRate,
      targetValue,
      minValue,
      maxValue,
      remainingTime
    };
    logger.debug(`Starting ${transitionType} transition interval with stepSize: ${changeRate}.`);
    switch (transitionType) {
      case "hue":
        this.internal.hueTransitionIntervalTimer?.stop();
        this.internal.currentHueTransitionData = {
          ...transitionData,
          isEnhancedHue: false,
          distanceLeft: hueDistanceLeft !== void 0 ? Math.abs(hueDistanceLeft) : void 0
        };
        this.internal.hueTransitionIntervalTimer?.start();
        break;
      case "enhancedHue":
        this.internal.hueTransitionIntervalTimer?.stop();
        this.internal.currentHueTransitionData = {
          ...transitionData,
          isEnhancedHue: true,
          distanceLeft: hueDistanceLeft !== void 0 ? Math.abs(hueDistanceLeft) : void 0
        };
        this.internal.hueTransitionIntervalTimer?.start();
        break;
      case "saturation":
        this.internal.saturationTransitionIntervalTimer?.stop();
        this.internal.currentSaturationTransitionData = transitionData;
        this.internal.saturationTransitionIntervalTimer?.start();
        break;
      case "colorTemperature":
        this.internal.colorTemperatureTransitionIntervalTimer?.stop();
        this.internal.currentColorTemperatureTransitionData = transitionData;
        this.internal.colorTemperatureTransitionIntervalTimer?.start();
        break;
      case "x":
        this.internal.xTransitionIntervalTimer?.stop();
        this.internal.currentXTransitionData = transitionData;
        this.internal.xTransitionIntervalTimer?.start();
        break;
      case "y":
        this.internal.yTransitionIntervalTimer?.stop();
        this.internal.currentYTransitionData = transitionData;
        this.internal.yTransitionIntervalTimer?.start();
        break;
    }
  }
  #hueStepIntervalTick() {
    const transitionData = this.internal.currentHueTransitionData;
    this.#stepIntervalTick(
      transitionData?.isEnhancedHue ? "enhancedHue" : "hue",
      this.internal.hueTransitionIntervalTimer,
      transitionData,
      transitionData?.isEnhancedHue ? this.state.enhancedCurrentHue : this.state.currentHue
    );
  }
  #saturationStepIntervalTick() {
    this.#stepIntervalTick(
      "saturation",
      this.internal.saturationTransitionIntervalTimer,
      this.internal.currentSaturationTransitionData,
      this.state.currentSaturation
    );
  }
  #colorTemperatureStepIntervalTick() {
    this.#stepIntervalTick(
      "colorTemperature",
      this.internal.colorTemperatureTransitionIntervalTimer,
      this.internal.currentColorTemperatureTransitionData,
      this.state.colorTemperatureMireds
    );
  }
  #xStepIntervalTick() {
    this.#stepIntervalTick(
      "x",
      this.internal.xTransitionIntervalTimer,
      this.internal.currentXTransitionData,
      this.state.currentX
    );
  }
  #yStepIntervalTick() {
    this.#stepIntervalTick(
      "y",
      this.internal.yTransitionIntervalTimer,
      this.internal.currentYTransitionData,
      this.state.currentY
    );
  }
  #stepIntervalTick(transitionType, transitionTimer, transitionData, currentValue) {
    if (transitionData === void 0) {
      transitionTimer?.stop();
      return;
    }
    if (transitionTimer === void 0) {
      transitionData.remainingTime = 0;
      return;
    }
    if ("isEnhancedHue" in transitionData && transitionData.isEnhancedHue && transitionType !== "enhancedHue") {
      throw new ImplementationError(
        "Transition data is for enhanced hue, but transition type is not enhanced hue"
      );
    }
    const { changeRate, targetValue, minValue, maxValue } = transitionData;
    let newValue;
    switch (transitionType) {
      case "hue":
      case "enhancedHue":
        const maxHueRange = transitionType === "hue" ? MAX_HUE_VALUE : MAX_ENHANCED_HUE_VALUE;
        newValue = addValueWithOverflow(
          currentValue,
          changeRate,
          minValue ?? MIN_HUE_VALUE,
          maxValue ?? maxHueRange
        );
        break;
      case "saturation":
        newValue = cropValueRange(
          currentValue + changeRate,
          minValue ?? MIN_SATURATION_VALUE,
          maxValue ?? MAX_SATURATION_VALUE
        );
        break;
      case "colorTemperature":
        newValue = cropValueRange(
          currentValue + changeRate,
          minValue ?? this.minimumColorTemperatureMireds,
          maxValue ?? this.maximumColorTemperatureMireds
        );
        break;
      case "x":
      case "y":
        newValue = cropValueRange(
          currentValue + changeRate,
          minValue ?? MIN_CIE_XY_VALUE,
          maxValue ?? MAX_CIE_XY_VALUE
        );
        break;
      default:
        throw new ImplementationError(`Unknown transition type: ${transitionType}`);
    }
    const isHueTransition = transitionType === "hue" || transitionType === "enhancedHue";
    if (isHueTransition) {
      logger.info(
        `Setting new value in ${transitionType} transition interval: ${newValue}, target: ${targetValue}, changeRate: ${changeRate}`
      );
      let distanceLeft = transitionData.distanceLeft;
      if (targetValue !== void 0 && distanceLeft !== void 0) {
        distanceLeft -= Math.abs(changeRate);
        if (distanceLeft <= 0) {
          logger.debug(`Stopping ${transitionType} transition interval at targetLevel: ${targetValue}.`);
          transitionTimer?.stop();
          transitionData.remainingTime = 0;
          newValue = targetValue;
        } else {
          transitionData.distanceLeft = distanceLeft;
        }
      }
      if (transitionTimer.isRunning) {
        logger.debug(
          `Setting new value in ${transitionType} transition interval: ${newValue} (distance left ${distanceLeft})`
        );
        if (distanceLeft === void 0) {
          transitionData.remainingTime = 65535;
        } else {
          transitionData.remainingTime = Math.floor(Math.ceil(distanceLeft / Math.abs(changeRate)) * 10);
        }
      }
    } else {
      if (minValue !== void 0 && newValue === minValue) {
        logger.debug(`Stopping ${transitionType} transition interval at minValue: ${minValue}.`);
        transitionTimer?.stop();
        transitionData.remainingTime = 0;
      } else if (maxValue !== void 0 && newValue === maxValue) {
        logger.debug(`Stopping ${transitionType} transition interval at maxLevel: ${maxValue}.`);
        transitionTimer?.stop();
        transitionData.remainingTime = 0;
      } else {
        logger.info(
          `Setting new value in ${transitionType} transition interval: ${newValue}, target: ${targetValue}, changeRate: ${changeRate}`
        );
        if (targetValue !== void 0) {
          if (changeRate > 0 && newValue >= targetValue) {
            logger.debug(`Stopping ${transitionType} transition interval at targetLevel: ${targetValue}.`);
            transitionTimer?.stop();
            transitionData.remainingTime = 0;
            newValue = targetValue;
          } else if (changeRate < 0 && newValue <= targetValue) {
            logger.debug(`Stopping ${transitionType} transition interval at targetLevel: ${targetValue}.`);
            transitionTimer?.stop();
            transitionData.remainingTime = 0;
            newValue = targetValue;
          }
        }
        if (transitionTimer.isRunning) {
          logger.debug(`Setting new value in ${transitionType} transition interval: ${newValue}`);
          if (targetValue === void 0) {
            transitionData.remainingTime = 65535;
          } else if (changeRate > 0) {
            transitionData.remainingTime = Math.floor(
              Math.ceil(((targetValue ?? maxValue) - newValue) / changeRate) * 10
            );
          } else {
            transitionData.remainingTime = Math.floor(
              Math.ceil((newValue - (targetValue ?? minValue)) / -changeRate) * 10
            );
          }
        }
      }
    }
    switch (transitionType) {
      case "hue":
        this.state.currentHue = newValue;
        break;
      case "enhancedHue":
        this.state.enhancedCurrentHue = newValue;
        break;
      case "saturation":
        this.state.currentSaturation = newValue;
        break;
      case "colorTemperature":
        this.state.colorTemperatureMireds = newValue;
        break;
      case "x":
        this.state.currentX = newValue;
        break;
      case "y":
        this.state.currentY = newValue;
        break;
    }
    this.#setRemainingTime();
  }
  #returnAsXyValue(value) {
    return value / 65536;
  }
  #returnAsOptionalXyValue(value) {
    if (value === void 0) {
      return void 0;
    }
    return this.#returnAsXyValue(value);
  }
  #setFromXyValue(fieldName, value) {
    if (value === void 0) {
      if (this.state[fieldName] === void 0) {
        return;
      } else {
        throw new ImplementationError(`${fieldName} cannot be set to undefined while being enabled.`);
      }
    }
    this.state[fieldName] = cropValueRange(Math.round(value * 65536), MIN_CIE_XY_VALUE, MAX_CIE_XY_VALUE);
  }
  async [Symbol.asyncDispose]() {
    this.internal.stopHueTransition();
    this.internal.stopSaturationTransition();
    this.internal.stopColorTemperatureTransition();
    this.internal.stopXTransition();
    this.internal.stopYTransition();
    await super[Symbol.asyncDispose]?.();
  }
}
((ColorControlServerLogic2) => {
  class Internal {
    /** Timer for the managed hue transition */
    hueTransitionIntervalTimer;
    /** Timer for the managed saturation transition */
    saturationTransitionIntervalTimer;
    /** Timer for the managed colorTemperature transition */
    colorTemperatureTransitionIntervalTimer;
    /** Timer for the managed x transition */
    xTransitionIntervalTimer;
    /** Timer for the managed y transition */
    yTransitionIntervalTimer;
    /** Structure to store the data of the current managed hue transition */
    currentHueTransitionData;
    /** Structure to store the data of the current managed saturation transition */
    currentSaturationTransitionData;
    /** Structure to store the data of the current managed color Temperature transition */
    currentColorTemperatureTransitionData;
    /** Structure to store the data of the current managed x transition */
    currentXTransitionData;
    /** Structure to store the data of the current managed y transition */
    currentYTransitionData;
    stopHueTransition() {
      this.hueTransitionIntervalTimer?.stop();
      if (this.currentHueTransitionData !== void 0) {
        this.currentHueTransitionData.remainingTime = 0;
      }
    }
    stopSaturationTransition() {
      this.saturationTransitionIntervalTimer?.stop();
      if (this.currentSaturationTransitionData !== void 0) {
        this.currentSaturationTransitionData.remainingTime = 0;
      }
    }
    stopColorTemperatureTransition() {
      this.colorTemperatureTransitionIntervalTimer?.stop();
      if (this.currentColorTemperatureTransitionData !== void 0) {
        this.currentColorTemperatureTransitionData.remainingTime = 0;
      }
    }
    stopXTransition() {
      this.xTransitionIntervalTimer?.stop();
      if (this.currentXTransitionData !== void 0) {
        this.currentXTransitionData.remainingTime = 0;
      }
    }
    stopYTransition() {
      this.yTransitionIntervalTimer?.stop();
      if (this.currentYTransitionData !== void 0) {
        this.currentYTransitionData.remainingTime = 0;
      }
    }
  }
  ColorControlServerLogic2.Internal = Internal;
  class State extends ColorControlServerBase.State {
    /**
     * The default implementation always set the target level immediately and so ignores all transition times
     * requested or configured.
     * Set this to true to manage transition times by changing the level value step wise every second. This is in
     * most cases not the best way because hardware supporting levels usually have ways to specify the change rate
     * or target value and transition time.
     */
    managedTransitionTimeHandling = false;
  }
  ColorControlServerLogic2.State = State;
})(ColorControlServerLogic || (ColorControlServerLogic = {}));
class ColorControlServer extends ColorControlServerLogic.for(ClusterType(ColorControl.Base)) {
}
export {
  ColorControlServer,
  ColorControlServerLogic
};
//# sourceMappingURL=ColorControlServer.js.map
