bramstroker__homeassistant-powercalc METASEP

utils/measure/ocr/run.sh METASEP
xhost +local:docker
docker run --rm --name=measure --env-file=../.env -v $(pwd)/export:/app/export -v $(pwd)/.persistent:/app/.persistent -e DISPLAY=192.168.178.195:0 -v /tmp/.X11-unix:/tmp/.X11-unix -it measure_ocr python3 ocr/main.py -t /usr/bin/tesseract -s rtp://@127.0.0.1:9988
xhost -local:docker

utils/measure/ocr/requirements.txt METASEP
numpy==1.22.3
opencv-python==4.5.5.64
packaging==21.3
Pillow==9.1.0
pyparsing==3.0.8
pytesseract==0.3.9

utils/measure/ocr/README.md METASEP

utils/measure/ocr/.gitignore METASEP
ocr/**
images
ocr_results.txt
*.log
utils/measure/.persistent/.gitkeep METASEP

utils/measure/powermeter/tuya.py METASEP
from __future__ import annotations

import time

import tuyapower

from .errors import PowerMeterError
from .powermeter import PowerMeasurementResult, PowerMeter

STATUS_OK = "OK"


class TuyaPowerMeter(PowerMeter):
    def __init__(
        self,
        device_id: str,
        device_ip: str,
        device_key: str,
        device_version: str = "3.3",
    ):
        self._device_id = device_id
        self._device_ip = device_ip
        self._device_key = device_key
        self._device_version = device_version

    def get_power(self) -> float:
        (on, w, mA, V, err) = tuyapower.deviceInfo(
            self._device_id, self._device_ip, self._device_key, self._device_version
        )

        if err != STATUS_OK:
            raise PowerMeterError("Could not get a succesfull power reading")

        return PowerMeasurementResult(w, time.time())

utils/measure/powermeter/tasmota.py METASEP
from __future__ import annotations

import time

import requests

from .errors import PowerMeterError
from .powermeter import PowerMeasurementResult, PowerMeter


class TasmotaPowerMeter(PowerMeter):
    def __init__(self, device_ip: str):
        self._device_ip = device_ip

    def get_power(self) -> PowerMeasurementResult:

        r = requests.get("http://{}/cm?cmnd=STATUS+8".format(self._device_ip), timeout=10)
        json = r.json()

        try:
            power = json["StatusSNS"]["ENERGY"]["Power"]
        except KeyError:
            raise PowerMeterError("Unexpected JSON response format")
        
        return PowerMeasurementResult(float(power), time.time())

utils/measure/powermeter/shelly.py METASEP
from __future__ import annotations

import logging
import time

import requests

from .errors import ConnectionError
from .powermeter import PowerMeasurementResult, PowerMeter

_LOGGER = logging.getLogger("measure")

class ShellyApi:
    status_endpoint = "/status"
    meter_endpoint = "/meter/0"

    def parse_json(self, json: str) -> tuple(float, float):
        pass

class ShellyApiGen1(ShellyApi):
    api_version = 1
    def parse_json(self, json) -> tuple(float, float):
        return (
            float(json["power"]),
            float(json["timestamp"])
        )

class ShellyApiGen2(ShellyApi):
    api_version = 2
    status_endpoint = "/rpc/Shelly.GetStatus"
    meter_endpoint = "/rpc/Switch.GetStatus?id=0"

    def parse_json(self, json) -> tuple(float, float):
        return (
            float(json["apower"]),
            time.time()
        )


class ShellyPowerMeter(PowerMeter):
    def __init__(self, shelly_ip: str, timeout: int = 5):
        self.timeout = timeout
        self.ip_address = shelly_ip
        self.api = self.detect_api_type()

    def get_power(self) -> PowerMeasurementResult:
        try:
            r = requests.get("http://{}{}".format(self.ip_address, self.api.meter_endpoint), timeout=self.timeout)
        except requests.RequestException as e:
            _LOGGER.error("Problem connecting to Shelly plug: %s", e)
            raise ConnectionError("Could not connect to Shelly Plug")

        json = r.json()
        power = self.api.parse_json(json)
        return PowerMeasurementResult(power[0], power[1])

    def detect_api_type(self) -> ShellyApi:
        for api in (ShellyApiGen1(), ShellyApiGen2()):
            try:
                uri = "http://{}{}".format(self.ip_address, api.status_endpoint)
                _LOGGER.debug(f"Checking API connection: {uri}")
                response = requests.get(uri, timeout=self.timeout)
            except requests.RequestException:
                _LOGGER.debug("Connection could not be established")
                continue

            if response.status_code != 200:
                _LOGGER.debug(f"Unexpected status code {response.status_code}")
                continue
        
            _LOGGER.debug(f"Shelly API version {api.api_version} detected")
            return api

        raise ConnectionError("Could not connect to Shelly Plug")

utils/measure/powermeter/powermeter.py METASEP
from __future__ import annotations

from typing import Any, NamedTuple


class PowerMeter:
    def get_power(self) -> PowerMeasurementResult:
        pass

    def get_questions(self) -> list[dict]:
        return []

    def process_answers(self, answers: dict[str, Any]):
        pass


class PowerMeasurementResult(NamedTuple):
    power: float
    updated: float

utils/measure/powermeter/ocr.py METASEP
from __future__ import annotations

import os

from .powermeter import PowerMeasurementResult, PowerMeter


class OcrPowerMeter(PowerMeter):
    def __init__(self) -> None:
        filepath = os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            "../ocr/ocr_results.txt"
        )

        self.file = open(filepath, "rb")
        super().__init__()

    def get_power(self) -> PowerMeasurementResult:
        last_line = self.read_last_line()
        (timestamp, power) = last_line.strip().split(";")
        power = float(power)
        timestamp = float(timestamp)
        return PowerMeasurementResult(power, timestamp)
    
    def read_last_line(self):
        try:
            self.file.seek(-2, os.SEEK_END)
            while self.file.read(1) != b'\n':
                self.file.seek(-2, os.SEEK_CUR)
        except OSError:
            self.file.seek(0)
        return self.file.readline().decode()

utils/measure/powermeter/manual.py METASEP
from __future__ import annotations

import time

from .powermeter import PowerMeasurementResult, PowerMeter


class ManualPowerMeter(PowerMeter):
    def get_power(self) -> PowerMeasurementResult:
        power = input('Input power measurement:')
        return PowerMeasurementResult(float(power), time.time())

utils/measure/powermeter/kasa.py METASEP
from __future__ import annotations

import asyncio
import time

from kasa import SmartPlug

from .powermeter import PowerMeasurementResult, PowerMeter


class KasaPowerMeter(PowerMeter):
    def __init__(self, device_ip: str):
        self._smartplug = SmartPlug(device_ip)

    def get_power(self) -> PowerMeasurementResult:
        loop = asyncio.get_event_loop()
        power = loop.run_until_complete(self.async_read_power_meter())
        return PowerMeasurementResult(power, time.time())

    async def async_read_power_meter(self):
        await self._smartplug.update()
        return self._smartplug.emeter_realtime["power"]

utils/measure/powermeter/hass.py METASEP
from __future__ import annotations

from typing import Any

import inquirer
from dateutil.parser import parse
from homeassistant_api import Client

from .errors import PowerMeterError
from .powermeter import PowerMeasurementResult, PowerMeter


class HassPowerMeter(PowerMeter):
    def __init__(self, api_url: str, token: str):
        try:
            self.client = Client(api_url, token)
        except Exception as e:
            raise PowerMeterError(f"Failed to connect to HA API: {e}")

    def get_power(self) -> PowerMeasurementResult:
        state = self.client.get_state(self._entity_id)
        last_updated = parse(state.get("last_updated")).timestamp()
        return PowerMeasurementResult(float(state.get("state")), last_updated)

    def get_questions(self) -> list[dict]:
        power_sensor_list = self.get_power_sensors()

        return [
            inquirer.List(
                name="powermeter_entity_id",
                message="Select the powermeter",
                choices=power_sensor_list
            )
        ]

    def get_power_sensors(self) -> list[str]:
        entities = self.client.get_entities()
        sensors = entities["sensor"].entities.values()
        power_sensors = [
            entity.entity_id for entity in sensors if 
            hasattr(entity.state, 'attributes') and 
            hasattr(entity.state.attributes, 'unit_of_measurement') and 
            entity.state.attributes['unit_of_measurement'] == "W"
        ]
        return sorted(power_sensors)

    def process_answers(self, answers: dict[str, Any]):
        self._entity_id = answers["powermeter_entity_id"]

utils/measure/powermeter/errors.py METASEP
class PowerMeterError(Exception):
    pass


class OutdatedMeasurementError(PowerMeterError):
    pass

class ZeroReadingError(PowerMeterError):
    pass

class ConnectionError(PowerMeterError):
    pass
utils/measure/powermeter/dummy.py METASEP
from __future__ import annotations

import time

from .powermeter import PowerMeasurementResult, PowerMeter


class DummyPowerMeter(PowerMeter):
    def get_power(self) -> PowerMeasurementResult:
        return PowerMeasurementResult(20.5, time.time())

utils/measure/powermeter/__init__.py METASEP

utils/measure/playground/test_var.py METASEP
def get_ct_variations():
    i = 0
    for bri in inclusive_range(1, 255, 15):
        for mired in inclusive_range(150, 500, 50):
            i = i + 1
    print("ct: " + str(i))

def get_hs_variations():
    i = 0
    for bri in inclusive_range(1, 255, 10):
        for sat in inclusive_range(1, 254, 10):
            for hue in inclusive_range(1, 65535, 2000):
                i = i + 1
    print("hs: " + str(i))

def get_brightness_variations():
    i = 0
    for bri in inclusive_range(1, 255, 3):
        i = i + 1
    print("bri: " + str(i))

def inclusive_range(start: int, end: int, step: int):
    i = start
    while i < end:
        yield i
        i += step
    yield end

get_ct_variations()
get_hs_variations()
get_brightness_variations()
utils/measure/playground/test_shellyPy.py METASEP
import ShellyPy

device = ShellyPy.Shelly("192.168.178.254")

# device.relay(0, turn=True)
while True:
    emeter = device.emeter("power")
    pass

utils/measure/playground/test_pyShelly.py METASEP
import pyShelly


def device_added(dev, code):
    print(dev, " ", code)


shelly = pyShelly.pyShelly()
print("version:", shelly.version())

shelly.cb_device_added.append(device_added)
shelly.start()
shelly.discover()

while True:
    pass

utils/measure/playground/test_phue.py METASEP
from phue import Bridge, PhueRegistrationException


def initialize_hue_bridge() -> Bridge:
    # f = open("bridge_user.txt", "r+")

    # authenticated_user = f.read()
    # if len(authenticated_user) > 0:
    #     bridge.username = authenticated_user

    try:
        bridge = Bridge("192.168.178.44")
        # bridge.connect()
    except PhueRegistrationException as err:
        print("Please click the link button on the bridge, than hit enter..")
        input()
        bridge = Bridge("192.168.178.44")

    return bridge


b = initialize_hue_bridge()

lights = b.lights

# Print light names
for l in lights:
    print(l.name)

utils/measure/ocr/ocr.py METASEP
import logging
import os
import sys
import threading
import time
from datetime import datetime
from decimal import Decimal, DecimalException
from pathlib import Path
from threading import Thread

import cv2
import numpy
import pytesseract

logging.basicConfig(
    level=logging.getLevelName("DEBUG"),
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(os.path.join(sys.path[0], "ocr.log")),
        logging.StreamHandler()
    ]
)

_LOGGER = logging.getLogger("ocr")

WINDOW_NAME = "Realtime OCR"

OCR_SLEEP = 0.5

def tesseract_location(root):
    """
    Sets the tesseract cmd root and exits is the root is not set correctly

    Tesseract needs a pointer to exec program included in the install.
    Example: User/Documents/tesseract/4.1.1/bin/tesseract
    See tesseract documentation for help.
    """
    try:
        pytesseract.pytesseract.tesseract_cmd = root
    except FileNotFoundError:
        print("Please double check the Tesseract file directory or ensure it's installed.")
        sys.exit(1)


class RateCounter:
    """
    Class for finding the iterations/second of a process

    `Attributes:`
        start_time: indicates when the time.perf_counter() began
        iterations: determines number of iterations in the process

    `Methods:`
        start(): Starts a time.perf_counter() and sets it in the self.start_time attribute
        increment(): Increases the self.iterations attribute
        rate(): Returns the iterations/seconds
    """

    def __init__(self):
        self.start_time = None
        self.iterations = 0

    def start(self):
        """
        Starts a time.perf_counter() and sets it in the self.start_time attribute

        :return: self
        """
        self.start_time = time.perf_counter()
        return self

    def increment(self):
        """
        Increases the self.iterations attribute
        """
        self.iterations += 1

    def rate(self):
        """
        Returns the iterations/seconds
        """
        elapsed_time = (time.perf_counter() - self.start_time)
        return self.iterations / elapsed_time

    def render(self, frame: numpy.ndarray, rate: float) -> numpy.ndarray:
        """
        Places text showing the iterations per second in the CV2 display loop.

        This is for demonstrating the effects of multi-threading.

        :param frame: CV2 display frame for text destination
        :param rate: Iterations per second rate to place on image

        :return: CV2 display frame with rate added
        """

        cv2.putText(frame, "{} Iterations/Second".format(int(rate)),
                    (10, 35), cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255))
        return frame

class VideoStream:
    """Class for grabbing frames from CV2 video capture. """

    def __init__(self, src=0):
        self.stream = cv2.VideoCapture(src)
        (self.grabbed, self.frame) = self.stream.read()
        if self.frame is None:
            print("Could not find camera")
            #exit(1)
        self.stopped = False
        cv2.namedWindow(WINDOW_NAME)

    def start(self):
        """
        Creates a thread targeted at get(), which reads frames from CV2 VideoCapture

        :return: self
        """
        Thread(target=self.get, args=()).start()
        return self

    def get(self):
        """
        Continuously gets frames from CV2 VideoCapture and sets them as self.frame attribute
        """
        while not self.stopped:
            (self.grabbed, self.frame) = self.stream.read()

    def get_video_dimensions(self):
        """
        Gets the width and height of the video stream frames

        :return: height `int` and width `int` of VideoCapture
        """
        width = self.stream.get(cv2.CAP_PROP_FRAME_WIDTH)
        height = self.stream.get(cv2.CAP_PROP_FRAME_HEIGHT)
        return int(width), int(height)

    def stop_process(self):
        """
        Sets the self.stopped attribute as True and kills the VideoCapture stream read
        """
        self.stopped = True
    
    def capture_image(self, frame: numpy.ndarray = None, captures=0):
        """
        Capture a .jpg during CV2 video stream. Saves to a folder /images in working directory.

        :param frame: CV2 frame to save
        :param captures: (optional) Number of existing captures to append to filename

        :return: Updated number of captures. If capture param not used, returns 1 by default
        """
        if frame is None:
            frame = self.frame

        cwd_path = os.getcwd()
        Path(cwd_path + '/images').mkdir(parents=False, exist_ok=True)

        now = datetime.now()
        # Example: "OCR 2021-04-8 at 12:26:21-1.jpg"  ...Handles multiple captures taken in the same second
        name = "OCR " + now.strftime("%Y-%m-%d") + " at " + now.strftime("%H:%M:%S") + '-' + str(captures + 1) + '.jpg'
        path = 'images/' + name
        cv2.imwrite(path, frame)
        captures += 1
        print(name)
        return captures
    
    

class OcrRegionSelection:
    def __init__(self, video_stream: VideoStream) -> None:
        self.selection = None 
        self.drag_start = None 
        self.is_selecting = False
        self.stream = video_stream
    
    def start(self):
        #return self
        """
        Creates a thread targeted at get(), which reads frames from CV2 VideoCapture

        :return: self
        """
        Thread(target=self.register_mouse_callback, args=()).start()
        return self
    
    def register_mouse_callback(self):
        cv2.setMouseCallback(WINDOW_NAME, self.draw_rectangle)

    # Method to track mouse events 
    def draw_rectangle(self, event, x, y, flags, param): 
        x, y = numpy.int16([x, y]) 

        if event == cv2.EVENT_LBUTTONDOWN:
            # Start selection
            if not self.is_selecting:
                self.drag_start = (x, y) 
                self.is_selecting = True
            # Confirm selection
            else:
                x_start, y_start = self.drag_start
                self.selection = (x_start, y_start, x, y)
                self.is_selecting = False
                self.stream.capture_image(self.get_cropped_frame(self.stream.frame))

        if event == cv2.EVENT_MOUSEMOVE:
            if self.is_selecting and self.drag_start:
                x_start, y_start = self.drag_start 
                self.selection = (x_start, y_start, x, y) 
    
    def render(self, frame: numpy.ndarray) -> numpy.ndarray:
        if self.selection:
            cv2.rectangle(frame, (self.selection[0], self.selection[1]), (self.selection[2], self.selection[3]), (0, 255, 0), 2)
        else:
            (h, w) = frame.shape[:2]
            y_center = h//2
            cv2.rectangle(frame, (100, y_center - 40), (1400, y_center + 90), (0,0,0), -1)
            cv2.putText(frame, "Start drawing the OCR region", (100, h//2), cv2.FONT_HERSHEY_DUPLEX, 1.5, (0, 255, 0))
            cv2.putText(frame, "Click, drag, and click another time to confirm", (100, h//2+50), cv2.FONT_HERSHEY_DUPLEX, 1.5, (0, 255, 0))
        return frame
    
    def has_selection(self):
        return self.selection is not None

    def get_cropped_frame(self, frame: numpy.ndarray) -> numpy.ndarray:
        return frame[self.selection[1]:self.selection[3], self.selection[0]:self.selection[2]]


class OCR:
    """Class for creating a pytesseract OCR process in a dedicated thread"""

    def __init__(self, video_stream: VideoStream, region_selection: OcrRegionSelection):
        self.measurement: Decimal = None
        self.stopped: bool = False
        self.region_selection = region_selection
        self.video_stream = video_stream
        self.file = None

    def start(self):
        """
        Creates a thread targeted at the ocr process
        :return: self
        """
        Thread(target=self.do_ocr, args=()).start()
        return self

    def do_ocr(self):
        """
        Creates a process where frames are continuously grabbed from the exchange and processed by pytesseract OCR.
        """
        while not self.stopped:
            if self.video_stream is not None and self.region_selection.has_selection():
                try:
                    frame = self.video_stream.frame
                    frame = self.region_selection.get_cropped_frame(frame)

                    #Convert to grayscale for easier OCR detection
                    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
                    # frame = cv2.adaptiveThreshold(frame, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
                    
                    match = pytesseract.image_to_string(
                        frame,
                        config="-c tessedit_char_whitelist='0123456789.'"
                    )
                    _LOGGER.debug(f"OCR match: {match.strip()}")
                    if len(match) > 0:
                        try:
                            measurement = Decimal(match)
                            _LOGGER.info(f"Measurement: {str(measurement)}")
                        except DecimalException:
                            _LOGGER.error("Cannot convert OCR match to decimal")
                            continue
                        if not self.validate_measurement(measurement):
                            continue
                        self.measurement = measurement
                        self.write_result(self.measurement)
                    time.sleep(OCR_SLEEP)
                except Exception as e:
                    _LOGGER.error(f"OCR error: {e}")
    
    def write_result(self, measurement: Decimal):
        if self.file is None:
            file_path = os.path.join(
                os.path.dirname(os.path.abspath(__file__)),
                "ocr_results.txt"
            )
            self.file = open(file_path, "a")
        
        self.file.write(f"{time.time()};{str(measurement)}\n")
        self.file.flush()

    def stop_process(self):
        """
        Sets the self.stopped attribute to True and kills the ocr() process
        """
        self.stopped = True
    
    def render(self, frame: numpy.ndarray) -> numpy.ndarray:
        if self.measurement is None:
            return frame
        frame = cv2.putText(frame, str(self.measurement), (100, 100), cv2.FONT_HERSHEY_DUPLEX, 1.5, (0, 255, 0))
        return frame
    
    def validate_measurement(self, measurement: Decimal) -> bool:
        if measurement > 100:
            _LOGGER.info("Measurement was too high, discarding")
            return False

        if measurement < 0.05:
            _LOGGER.info("Measurement was too low, discarding")
            return False

        if self.measurement:
            diff_percentage = self.get_percentage_change(self.measurement, measurement)
            _LOGGER.debug(f"Percentage diff: {diff_percentage}")
            if diff_percentage > 120:
                _LOGGER.info("Difference between measurements is too high, this must be wrong")
                return False
        
        return True
    
    def get_percentage_change(self, current: Decimal, previous: Decimal) -> int:
        try:
            if current == previous:
                return 0
            elif(current > previous):
                return int((abs(current - previous) / previous) * 100)
            else:
                return int((abs(previous - current) / current) * 100)
        except ZeroDivisionError:
            return 100000


def ocr_stream(source: str = "0"):
    """
    Begins the video stream and text OCR in two threads, then shows the video in a CV2 frame with the OCR
    boxes overlaid in real-time.
    """

    video_stream = VideoStream(source).start()  # Starts reading the video stream in dedicated thread
    region_selection = OcrRegionSelection(video_stream).start()
    ocr = OCR(video_stream, region_selection).start()  # Starts optical character recognition in dedicated thread
    cps1 = RateCounter().start()

    print("OCR stream started")
    print("Active threads: {}".format(threading.activeCount()))

    # Main display loop
    print("\nPUSH q TO VIEW VIDEO STREAM\n")
    i = 0
    while True:
        i += 1

        # Quit condition:
        pressed_key = cv2.waitKey(1) & 0xFF
        if pressed_key == ord('q'):
            video_stream.stop_process()
            ocr.stop_process()
            print("OCR stream stopped\n")
            break

        frame = video_stream.frame

        frame = cps1.render(frame, cps1.rate())
        frame = ocr.render(frame)
        frame = region_selection.render(frame)

        cv2.imshow(WINDOW_NAME, frame)
        cps1.increment()

utils/measure/ocr/main.py METASEP
import argparse

import ocr


def main():
    """
    Handles command line arguments and begins the real-time OCR by calling ocr_stream().
    A path to the Tesseract cmd root is required.
    """
    parser = argparse.ArgumentParser()

    requiredNamed = parser.add_argument_group('required named arguments')

    requiredNamed.add_argument('-t', '--tess_path',
                               help="path to the cmd root of tesseract install (see docs for further help)",
                               metavar='', required=True)
    parser.add_argument("-s", "--src", help="SRC video source for video capture",
                        default=0, type=str)

    args = parser.parse_args()

    ocr.tesseract_location(args.tess_path)
    ocr.ocr_stream(source=args.src)


if __name__ == '__main__':
    main()  # '/usr/local/Cellar/tesseract/4.1.1/bin/tesseract'
utils/measure/light_controller/hue.py METASEP
from __future__ import annotations

import os
from typing import Any

import inquirer
from phue import Bridge, PhueRegistrationException

from .controller import LightController, LightInfo
from .errors import LightControllerError, ModelNotDiscoveredError

NAME = "hue"
TYPE_LIGHT = "light"
TYPE_GROUP = "group"

class HueLightController(LightController):
    def __init__(self, bridge_ip: str):
        self.bridge = self.initialize_hue_bridge(bridge_ip)
        self.lights = {light.light_id: light.name for light in self.bridge.lights}
        self.groups = {group.group_id: group.name for group in self.bridge.groups}

    def change_light_state(self, color_mode: str, on: bool = True, **kwargs):
        kwargs["on"] = on
        if self.is_group:
            self.bridge.set_group(self.light_id, kwargs)
        else:
            self.bridge.set_light(self.light_id, kwargs)

    def get_light_info(self) -> LightInfo:
        if self.is_group:
            model_id = self.find_group_model(self.light_id)
            return LightInfo(
                model_id=model_id,
            )
        
        # Individual light information
        light = self.bridge.get_light(self.light_id)
        lightinfo = LightInfo(
            model_id=light["modelid"],
        )

        if "ct" in light["capabilities"]["control"]:
            lightinfo.min_mired = light["capabilities"]["control"]["ct"]["min"]
            lightinfo.max_mired = light["capabilities"]["control"]["ct"]["max"]

        return lightinfo

    def find_group_model(self, group_id: str) -> str:
        model_ids = set()
        for light_id in self.bridge.get_group(group_id, "lights"):
            light = self.bridge.get_light(int(light_id))
            model_id = light["modelid"]
            model_ids.add(model_id)

        if len(model_ids) == 0:
            raise ModelNotDiscoveredError("Could not find a model id for the group")
        
        if len(model_ids) > 1:
            raise LightControllerError("The Hue group contains lights of multiple models, this is not supported")
        
        return model_ids.pop()


    def initialize_hue_bridge(self, bridge_ip: str) -> Bridge:
        config_file_path = os.path.join(os.path.dirname(__file__), "../.persistent/.python_hue")
        try:
            bridge = Bridge(ip=bridge_ip, config_file_path=config_file_path)
        except PhueRegistrationException as err:
            print("Please click the link button on the bridge, than hit enter..")
            input()
            bridge = Bridge(ip=bridge_ip, config_file_path=config_file_path)

        return bridge

    def get_questions(self) -> list[dict]:

        def get_message(answers) -> str:
            if answers.get("multiple_lights"):
                return "Select the lightgroup"
            return "Select the light"

        def get_light_list(answers) -> list:
            if answers.get("multiple_lights"):
                return [(name, f"{TYPE_GROUP}:{id}") for id, name in self.groups.items()]
            return [(name, f"{TYPE_LIGHT}:{id}") for id, name in self.lights.items()]

        return [
            inquirer.List(
                name="light",
                message=get_message,
                choices=get_light_list
            ),
        ]

    def process_answers(self, answers: dict[str, Any]):
        light_type, light_id = answers["light"].split(":")
        self.is_group = light_type == TYPE_GROUP
        self.light_id = int(light_id)

utils/measure/light_controller/hass.py METASEP
from __future__ import annotations

from typing import Any

import inquirer
from homeassistant_api import Client

from .const import MAX_MIRED, MIN_MIRED, MODE_COLOR_TEMP, MODE_HS
from .controller import LightController, LightInfo
from .errors import LightControllerError

NAME = "hass"


class HassLightController(LightController):
    def __init__(self, api_url: str, token: str):
        try:
            self.client = Client(api_url, token)
        except Exception as e:
            raise LightControllerError(f"Failed to connect to HA API: {e}")

    def change_light_state(self, color_mode: str, on: bool = True, **kwargs):
        if on == False:
            self.client.trigger_service('light', 'turn_off', entity_id=self._entity_id)
            return

        if color_mode == MODE_HS:
            json = self.build_hs_json_body(**kwargs)
        elif color_mode == MODE_COLOR_TEMP:
            json = self.build_ct_json_body(**kwargs)
        else:
            json = self.build_bri_json_body(**kwargs)

        self.client.trigger_service('light', 'turn_on', **json)

    def get_light_info(self) -> LightInfo:
        state = self.client.get_state(self._entity_id)
        min_mired = state.attributes.get("min_mireds") or MIN_MIRED
        max_mired = state.attributes.get("max_mireds") or MAX_MIRED
        return LightInfo(self._model_id, min_mired, max_mired)

    def get_questions(self) -> list[dict]:
        entities = self.client.get_entities()
        lights = entities["light"].entities.values()
        light_list = sorted([entity.entity_id for entity in lights])

        return [
            inquirer.List(
                name="light_entity_id",
                message="Select the light",
                choices=light_list
            ),
            inquirer.Text(
                name="light_model_id",
                message="What model is your light? Ex: LED1837R5",
                validate=lambda _, x: len(x) > 0,
            )
        ]

    def process_answers(self, answers: dict[str, Any]):
        self._entity_id = answers["light_entity_id"]
        self._model_id = answers["light_model_id"]

    def build_hs_json_body(self, bri: int, hue: int, sat: int) -> dict:
        return {
            "entity_id": self._entity_id,
            "brightness": bri,
            "hs_color": [hue / 65535 * 360, sat / 255 * 100],
        }

    def build_ct_json_body(self, bri: int, ct: int) -> dict:
        return {"entity_id": self._entity_id, "brightness": bri, "color_temp": ct}

    def build_bri_json_body(self, bri: int) -> dict:
        return {"entity_id": self._entity_id, "brightness": bri}

utils/measure/light_controller/errors.py METASEP
class LightControllerError(Exception):
    pass

class ModelNotDiscoveredError(LightControllerError):
    pass
utils/measure/light_controller/controller.py METASEP
from __future__ import annotations

from typing import Any

from .const import MAX_MIRED, MIN_MIRED


class LightInfo:
    model_id: str

    def __init__(self, model_id: str, min_mired: int = MIN_MIRED, max_mired: int = MAX_MIRED):
        self.model_id = model_id
        self._min_mired = min_mired
        self._max_mired = max_mired

    def get_min_mired(self) -> int:
        return self._min_mired

    def set_min_mired(self, value: int):
        if value < MIN_MIRED:
            value = MIN_MIRED
        self._min_mired = value

    def get_max_mired(self) -> int:
        return self._max_mired

    def set_max_mired(self, value: int):
        if value > MAX_MIRED:
            value = MAX_MIRED
        self._max_mired = value

    min_mired = property(get_min_mired, set_min_mired)
    max_mired = property(get_max_mired, set_max_mired)


class LightController:
    def change_light_state(self, color_mode: str, on: bool = True, **kwargs):
        pass

    def get_light_info(self) -> LightInfo:
        return LightInfo("")

    def get_questions(self) -> list[dict]:
        return []

    def process_answers(self, answers: dict[str, Any]):
        pass

utils/measure/light_controller/const.py METASEP
MODE_HS = "hs"
MODE_COLOR_TEMP = "color_temp"
MODE_BRIGHTNESS = "brightness"

MIN_MIRED = 150
MAX_MIRED = 500

utils/measure/light_controller/__init__.py METASEP

.github/scripts/supported_models/generate_file.py METASEP
"""Update the manifest file."""
import glob
import json
import os
import sys
from pathlib import Path
from typing import Optional

sys.path.insert(
    1,
    os.path.abspath(
        os.path.join(Path(__file__), "../../../../custom_components/powercalc")
    ),
)

from aliases import MANUFACTURER_DIRECTORY_MAPPING, MODEL_DIRECTORY_MAPPING
from pytablewriter import MarkdownTableWriter


def generate_supported_model_list():
    writer = MarkdownTableWriter()
    writer.header_list = [
        "manufacturer",
        "model id",
        "name",
        "calculation modes",
        "color modes",
        "aliases",
    ]

    """Generate static file containing the supported models."""
    project_root = os.path.realpath(
        os.path.join(os.path.abspath(__file__), "../../../../")
    )
    data_dir = f"{project_root}/custom_components/powercalc/data"
    with open(os.path.join(project_root, "docs/supported_models.md"), "w") as md_file:

        rows = []
        for json_path in glob.glob(
            f"{data_dir}/*/*/model.json",
            recursive=True,
        ):
            with open(json_path) as json_file:
                model_directory = os.path.dirname(json_path)
                model_data = json.load(json_file)
                model = os.path.basename(model_directory)
                manufacturer = os.path.basename(os.path.dirname(model_directory))
                supported_modes = model_data["supported_modes"]
                name = model_data["name"]
                color_modes = get_color_modes(model_directory, data_dir, model_data)
                aliases = get_aliases(manufacturer, model)
                rows.append(
                    [
                        manufacturer,
                        model,
                        name,
                        ",".join(supported_modes),
                        ",".join(color_modes),
                        ",".join(aliases),
                    ]
                )

        rows = sorted(rows, key=lambda x: (x[0], x[1]))
        writer.value_matrix = rows
        writer.table_name = f"Supported models ({len(rows)} total)"
        writer.dump(md_file)
    print("Generated supported_models.md")


def get_color_modes(model_directory: str, data_dir: str, model_data: dict) -> list:
    if "linked_lut" in model_data:
        model_directory = os.path.join(data_dir, model_data["linked_lut"])

    color_modes = set()
    for path in glob.glob(f"{model_directory}/**/*.csv.gz", recursive=True):
        filename = os.path.basename(path)
        index = filename.index(".")
        color_mode = filename[:index]
        color_modes.add(color_mode)
    return color_modes


def get_aliases(manufacturer_dir: str, model: str) -> list:
    manufacturer = get_manufacturer_by_directory_name(manufacturer_dir)
    if manufacturer is None:
        return []

    model_aliases = MODEL_DIRECTORY_MAPPING.get(manufacturer)
    if model_aliases is None:
        return []

    aliases = list()
    for alias, model_id in model_aliases.items():
        if model == model_id:
            aliases.append(alias)

    return aliases


def get_manufacturer_by_directory_name(search_directory: str) -> Optional[str]:
    for manufacturer, directory in MANUFACTURER_DIRECTORY_MAPPING.items():
        if search_directory == directory:
            return manufacturer

    return None


generate_supported_model_list()

custom_components/powercalc/data/yeelight/YLDL01YL/downlight/model.json METASEP
{
    "standby_power": 0.72
}
custom_components/powercalc/data/yeelight/YLDL01YL/downlight/color_temp.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDL01YL/ambilight/model.json METASEP
{
    "standby_power": 0.72
}
custom_components/powercalc/data/yeelight/YLDL01YL/ambilight/hs.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDL01YL/ambilight/color_temp.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDD05YL/extensions_2/model.json METASEP
{
    "standby_power": 0.69
}

custom_components/powercalc/data/yeelight/YLDD05YL/extensions_2/hs.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDD05YL/extensions_2/color_temp.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDD05YL/extensions_0/model.json METASEP
{
    "standby_power": 0.69
}

custom_components/powercalc/data/yeelight/YLDD05YL/extensions_0/hs.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDD05YL/extensions_0/color_temp.csv.gz METASEP

custom_components/powercalc/data/tuya/NO66-ZB/length_5/model.json METASEP
{
    "standby_power": 1.05
}

custom_components/powercalc/data/tuya/NO66-ZB/length_5/hs.csv.gz METASEP

custom_components/powercalc/data/tuya/NO66-ZB/length_5/color_temp.csv.gz METASEP

custom_components/powercalc/data/tuya/NO66-ZB/length_2/model.json METASEP
{
    "standby_power": 1.05
}

custom_components/powercalc/data/tuya/NO66-ZB/length_2/hs.csv.gz METASEP

custom_components/powercalc/data/tuya/NO66-ZB/length_2/color_temp.csv.gz METASEP

custom_components/powercalc/data/tuya/NO66-ZB/length_1/model.json METASEP
{
    "standby_power": 1.05
}

custom_components/powercalc/data/tuya/NO66-ZB/length_1/hs.csv.gz METASEP

custom_components/powercalc/data/tuya/NO66-ZB/length_1/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_9/model.json METASEP
{
    "standby_power": 0.70
}
custom_components/powercalc/data/lifx/LIFX Z/length_9/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_9/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_8/model.json METASEP
{
    "standby_power": 0.69
}
custom_components/powercalc/data/lifx/LIFX Z/length_8/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_8/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_7/model.json METASEP
{
    "standby_power": 0.68
}
custom_components/powercalc/data/lifx/LIFX Z/length_7/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_7/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_6/model.json METASEP
{
    "standby_power": 0.67
}
custom_components/powercalc/data/lifx/LIFX Z/length_6/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_6/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_5/model.json METASEP
{
    "standby_power": 0.66
}
custom_components/powercalc/data/lifx/LIFX Z/length_5/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_5/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_4/model.json METASEP
{
    "standby_power": 0.62
}
custom_components/powercalc/data/lifx/LIFX Z/length_4/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_4/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_3/model.json METASEP
{
    "standby_power": 0.59
}
custom_components/powercalc/data/lifx/LIFX Z/length_3/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_3/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_2/model.json METASEP
{
    "standby_power": 0.57
}
custom_components/powercalc/data/lifx/LIFX Z/length_2/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_2/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_10/model.json METASEP
{
    "standby_power": 0.72
}
custom_components/powercalc/data/lifx/LIFX Z/length_10/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_10/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_1/model.json METASEP
{
    "standby_power": 0.61
}
custom_components/powercalc/data/lifx/LIFX Z/length_1/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/length_1/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_off/model.json METASEP
{
    "standby_power": 0.72
}
custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_off/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_off/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_50/model.json METASEP
{
    "standby_power": 4.86
}
custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_50/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_50/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_25/model.json METASEP
{
    "standby_power": 2.67
}
custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_25/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_25/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_100/model.json METASEP
{
    "standby_power": 8.53
}
custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_100/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/infrared_100/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX A19 Night Vision/infrared_off/model.json METASEP
{
    "standby_power": 0.47
}

custom_components/powercalc/data/lifx/LIFX A19 Night Vision/infrared_off/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX A19 Night Vision/infrared_50/model.json METASEP
{
    "standby_power": 4.36
}
custom_components/powercalc/data/lifx/LIFX A19 Night Vision/infrared_50/hs.csv.gz METASEP

utils/measure/requirements.txt METASEP
aiohttp==3.7.4.post0
anyio==3.3.0
async-timeout==3.0.1
asyncclick==7.1.2.3
attrs==21.2.0
blessed==1.19.1
certifi==2021.5.30
chardet==4.0.0
charset-normalizer==2.0.4
gensim==3.8.1
HomeAssistant-API==2.4.0.post2
idna==3.2
imageio==2.19.0
importlib-metadata==4.8.1
imutils==0.5.4
inquirer==2.9.2
multidict==5.1.0
networkx==2.8
numpy==1.22.3
opencv-python==4.5.5.64
packaging==21.3
phue==1.1
Pillow==9.1.0
prompt-toolkit==1.0.14
pycryptodome==3.10.1
Pygments==2.10.0
pyparsing==3.0.8
pytesseract==0.3.9
python-dateutil==2.8.2
python-decouple==3.4
python-editor==1.0.4
python-kasa==0.4.0.dev3
PyWavelets==1.3.0
readchar==3.0.5
regex==2021.8.28
requests==2.26.0
scikit-image==0.19.2
scipy==1.8.0
simplejson==3.17.5
six==1.16.0
smart-open==5.2.1
sniffio==1.2.0
tifffile==2022.5.4
tinytuya==1.2.7
tuyapower==0.0.25
typing-extensions==3.10.0.2
urllib3==1.26.6
wcwidth==0.2.5
yarl==1.6.3
zipp==3.5.0

utils/measure/README.md METASEP
# Powercalc measure tooling

This package contains everything you need to automatically take measurements of lights to contribute to this repository.

## Setup

There are two ways to run the measure script, using Docker and natively using Python.
The recommended way is with docker as all the needed dependencies are bundled.

### Docker

**Prerequisites:**
- Install docker engine, see https://docs.docker.com/get-docker/
- Start up the command line and verify docker is running: `docker version`
- Create a new directory. for example `powercalc-measure`
- Go to this directory and copy the `.env.dist` file to `.env`. You can find it [here](https://github.com/bramstroker/homeassistant-powercalc/blob/master/utils/measure/.env.dist)
- Modify the `.env` file to your needs. Selecting the `POWER_METER` and `LIGHT_CONTROLLER` here is mandatory

#### Start measurements

Go to the directory you created in a command line and run:

`docker run --rm --name=measure --env-file=.env -v $(pwd)/export:/app/export -v $(pwd)/.persistent:/app/.persistent -it bramgerritsen/powercalc-measure:latest`

If running Docker on Windows, replace `$(pwd)` with `%CD%`.

The script will ask you a few questions, than proceed taking measurements.

After the measurements are finished you will find the files in `export` directory.

To update to the latest version of the script:

`docker pull bramgerritsen/powercalc-measure`

### Native

Use this installation method when the docker method is not working for you or you want to do any development on the script.

**Prerequisites:**
- Make sure you have Python 3 running on your system. Currently the script is tested with version 3.8, 3.9 and 3.10.

Setup requirements for the script.
 It is advised to run in a virtual environment.
```
cd utils/measure
python3 -m venv measure
source measure/bin/activate
pip install -r requirements.txt
```

Alternatively use pyenv (https://github.com/pyenv/pyenv)
```
cd utils/measure
pyenv virtualenv 3.10.4 measure
pyenv activate measure
pip install -r requirements.txt
```

When this is not working on your machine (i.e. windows) just install globally.
```
cd utils/measure
pip install -r requirements.txt
```

#### Start measurements

```
python3 measure.py
```

The script will ask you a few questions, than proceed taking measurements.

After the measurements are finished you will find the files in `export` directory.

## More information about measuring

See the WIKI article for further documentation https://github.com/bramstroker/homeassistant-powercalc/wiki/Contributing-new-lights

## Building and running docker image locally
docker build -t measure .
docker run --rm --name=measure --env-file=.env -v $(pwd)/export:/app/export -v $(pwd)/.persistent:/app/.persistent -it measure

utils/measure/Dockerfile METASEP
# syntax=docker/dockerfile:1

FROM python:3.10-slim-buster

WORKDIR /app

COPY requirements.txt requirements.txt
RUN apt-get update && apt-get install gcc -y
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

COPY . .

CMD [ "python3", "measure.py"]

utils/measure/.gitignore METASEP
.env
export/
measure
light_controller/.python_hue
measure.log
.persistent
!/**/.gitkeep

utils/measure/.env.dist METASEP
# Set the power meter to use (hass, shelly, tuya, kasa, manual, tasmota)
POWER_METER=shelly

# Set the light controller to use (hass, hue)
LIGHT_CONTROLLER=hue

# Set the log level (CRITICAL, ERROR, WARNING, INFO, DEBUG)
LOG_LEVEL=INFO

# time between changing the light params and taking the measurement
SLEEP_TIME=3

# time between taking multiple samples for the same light setting
SLEEP_TIME_SAMPLE=3

# additional time to wait for each significant change in hue
SLEEP_TIME_HUE=2 

# additional time to wait for each significant change in saturation
SLEEP_TIME_SAT=2

# additional time to wait for each significant change in color temperature
SLEEP_TIME_CT=1

# Set to a value higher than 1 to take multiple samples to reduce noise
SAMPLE_COUNT=2

# Precision for the HS mode loop. You can increase it up to 4 to improve the precision
# of the profile by taking more measurements

HS_BRI_PRECISION=1.0
HS_HUE_PRECISION=1.0
HS_SAT_PRECISION=1.0

# Shelly
SHELLY_IP=x.x.x.x
SHELLY_TIMEOUT=60

# Tuya
TUYA_DEVICE_ID=aaaaaaaaad89682385bbb
TUYA_DEVICE_IP=x.x.x.x
TUYA_DEVICE_KEY=aaaaaaaae1b8abb
TUYA_DEVICE_VERSION=3.3

# Hue
HUE_BRIDGE_IP=x.x.x.x

# Home assistant
HASS_URL=http://x.x.x.x:8123/api
HASS_TOKEN=your_access_token

# Tasmota
TASMOTA_DEVICE_IP=x.x.x.x

# Kasa
KASA_DEVICE_IP=x.x.x.x

# Predefine answers to questions
# COLOR_MODE=color_temp
# GENERATE_MODEL_JSON=true
# GZIP=true
# MULTIPLE_LIGHTS=false
# LIGHT_ENTITY_ID=xx
# MEASURE_DEVICE=Shelly Plug S
# NUM_LIGHTS=1
# LIGHT_MODEL_ID=xx
# MODEL_NAME=xx
# DUMMY_LOAD=true

utils/measure/.VERSION METASEP
master
utils/measure/measure.py METASEP
from __future__ import annotations

import csv
import gzip
import json
import logging
import os
import re
import shutil
import sys
import time
from dataclasses import asdict, dataclass
from datetime import datetime as dt
from io import TextIOWrapper
from pathlib import Path
from typing import Any, Iterator, Optional

import inquirer
from decouple import Choices, UndefinedValueError, config
from inquirer.errors import ValidationError
from inquirer.questions import Question
from light_controller.const import MODE_BRIGHTNESS, MODE_COLOR_TEMP, MODE_HS
from light_controller.controller import LightController
from light_controller.errors import LightControllerError
from light_controller.hass import HassLightController
from light_controller.hue import HueLightController
from powermeter.dummy import DummyPowerMeter
from powermeter.errors import (
    OutdatedMeasurementError,
    PowerMeterError,
    ZeroReadingError,
)
from powermeter.hass import HassPowerMeter
from powermeter.kasa import KasaPowerMeter
from powermeter.manual import ManualPowerMeter
from powermeter.ocr import OcrPowerMeter
from powermeter.powermeter import PowerMeter
from powermeter.shelly import ShellyPowerMeter
from powermeter.tasmota import TasmotaPowerMeter
from powermeter.tuya import TuyaPowerMeter

CSV_HEADERS = {
    MODE_HS: ["bri", "hue", "sat", "watt"],
    MODE_COLOR_TEMP: ["bri", "mired", "watt"],
    MODE_BRIGHTNESS: ["bri", "watt"],
}

MIN_BRIGHTNESS = min(max(
    config(
        "MIN_BRIGHTNESS",
        default=config("START_BRIGHTNESS", default=1, cast=int),
        cast=int
    ), 1), 255
)
MAX_BRIGHTNESS = 255
MIN_SAT = min(max(config("MIN_SAT", default=1, cast=int), 1), 255)
MAX_SAT = min(max(config("MAX_SAT", default=255, cast=int), 1), 255)
MIN_HUE = min(max(config("MIN_HUE", default=1, cast=int), 1), 65535)
MAX_HUE = min(max(config("MAX_HUE", default=65535, cast=int), 1), 65535)
CT_BRI_STEPS = min(config("CT_BRI_STEPS", default=5, cast=int), 10)
CT_MIRED_STEPS = min(config("CT_MIRED_STEPS", default=10, cast=int), 10)
BRI_BRI_STEPS = 1

HS_BRI_PRECISION = config("HS_BRI_PRECISION", default=1, cast=float)
HS_BRI_PRECISION = min(HS_BRI_PRECISION, 4)
HS_BRI_PRECISION = max(HS_BRI_PRECISION, 0.5)
HS_BRI_STEPS = round(32 / HS_BRI_PRECISION)
del HS_BRI_PRECISION

HS_HUE_PRECISION = config("HS_HUE_PRECISION", default=1, cast=float)
HS_HUE_PRECISION = min(HS_HUE_PRECISION, 4)
HS_HUE_PRECISION = max(HS_HUE_PRECISION, 0.5)
HS_HUE_STEPS = round(2731 / HS_HUE_PRECISION)
del HS_HUE_PRECISION

HS_SAT_PRECISION = config("HS_SAT_PRECISION", default=1, cast=float)
HS_SAT_PRECISION = min(HS_SAT_PRECISION, 4)
HS_SAT_PRECISION = max(HS_SAT_PRECISION, 0.5)
HS_SAT_STEPS = round(32 / HS_SAT_PRECISION)
del HS_SAT_PRECISION

POWER_METER_DUMMY = "dummy"
POWER_METER_HASS = "hass"
POWER_METER_KASA = "kasa"
POWER_METER_MANUAL = "manual"
POWER_METER_OCR = "ocr"
POWER_METER_SHELLY = "shelly"
POWER_METER_TASMOTA = "tasmota"
POWER_METER_TUYA = "tuya"
POWER_METERS = [
    POWER_METER_DUMMY,
    POWER_METER_HASS,
    POWER_METER_KASA,
    POWER_METER_MANUAL,
    POWER_METER_OCR,
    POWER_METER_SHELLY,
    POWER_METER_TASMOTA,
    POWER_METER_TUYA,
]

SELECTED_POWER_METER = config("POWER_METER", cast=Choices(POWER_METERS))

LIGHT_CONTROLLER_DUMMY = "dummy"
LIGHT_CONTROLLER_HUE = "hue"
LIGHT_CONTROLLER_HASS = "hass"
LIGHT_CONTROLLERS = [
    LIGHT_CONTROLLER_DUMMY,
    LIGHT_CONTROLLER_HUE,
    LIGHT_CONTROLLER_HASS
]

SELECTED_LIGHT_CONTROLLER = config("LIGHT_CONTROLLER", cast=Choices(LIGHT_CONTROLLERS))

LOG_LEVEL = config("LOG_LEVEL", default=logging.INFO)
SLEEP_INITIAL = 10
SLEEP_STANDBY = config("SLEEP_STANDBY", default=20, cast=int)
SLEEP_TIME = config("SLEEP_TIME", default=2, cast=int)
SLEEP_TIME_SAMPLE = config("SLEEP_TIME_SAMPLE", default=1, cast=int)
SLEEP_TIME_HUE = config("SLEEP_TIME_HUE", default=5, cast=int)
SLEEP_TIME_SAT = config("SLEEP_TIME_SAT", default=10, cast=int)
SLEEP_TIME_CT = config("SLEEP_TIME_CT", default=10, cast=int)
MAX_RETRIES = config("MAX_RETRIES", default=5, cast=int)
SAMPLE_COUNT = config("SAMPLE_COUNT", default=1, cast=int)

SHELLY_IP = config("SHELLY_IP")
SHELLY_TIMEOUT = config("SHELLY_TIMEOUT", default=5, cast=int)
TUYA_DEVICE_ID = config("TUYA_DEVICE_ID")
TUYA_DEVICE_IP = config("TUYA_DEVICE_IP")
TUYA_DEVICE_KEY = config("TUYA_DEVICE_KEY")
TUYA_DEVICE_VERSION = config("TUYA_DEVICE_VERSION", default="3.3")
HUE_BRIDGE_IP = config("HUE_BRIDGE_IP")
HASS_URL = config("HASS_URL")
HASS_TOKEN = config("HASS_TOKEN")
TASMOTA_DEVICE_IP = config("TASMOTA_DEVICE_IP")
KASA_DEVICE_IP = config("KASA_DEVICE_IP")

CSV_ADD_DATETIME_COLUMN = config("CSV_ADD_DATETIME_COLUMN", default=False, cast=bool)

# Change some settings when selected power meter is manual
if SELECTED_POWER_METER == POWER_METER_MANUAL:
    SAMPLE_COUNT = 1
    BRI_BRI_STEPS = 3
    CT_BRI_STEPS = 15
    CT_MIRED_STEPS = 50

CSV_WRITE_BUFFER = 50
MAX_ALLOWED_0_READINGS = 50

logging.basicConfig(
    level=logging.getLevelName(LOG_LEVEL),
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(os.path.join(sys.path[0], "measure.log")),
        logging.StreamHandler()
    ]
)

_LOGGER = logging.getLogger("measure")

with open(os.path.join(sys.path[0], ".VERSION"), "r") as f:
    _VERSION = f.read().strip()

class Measure:
    def __init__(self, light_controller: LightController, power_meter: PowerMeter):
        self.light_controller = light_controller
        self.power_meter = power_meter
        self.num_0_readings: int = 0

    def start(self):
        """Starts the measurement session"""
        answers = self.ask_questions()
        self.light_controller.process_answers(answers)
        self.power_meter.process_answers(answers)
        self.color_mode = answers["color_mode"]
        self.num_lights = int(answers.get("num_lights", 1))
        self.is_dummy_load_connected = bool(answers.get("dummy_load"))
        if self.is_dummy_load_connected:
            self.dummy_load_value = self.get_dummy_load_value()
            _LOGGER.info(f"Using {self.dummy_load_value}W as dummy load value")

        self.light_info = self.light_controller.get_light_info()

        export_directory = os.path.join(
            os.path.dirname(__file__), "export", self.light_info.model_id
        )
        if not os.path.exists(export_directory):
            os.makedirs(export_directory)

        csv_file_path = f"{export_directory}/{self.color_mode}.csv"

        resume_at = None
        file_write_mode = "w"
        write_header_row = True
        if self.should_resume(csv_file_path):
            _LOGGER.info("Resuming measurements")
            resume_at = self.get_resume_variation(csv_file_path)
            file_write_mode = "a"
            write_header_row = False

        variations = list(self.get_variations(self.color_mode, resume_at))
        num_variations = len(variations)

        _LOGGER.info(f"Starting measurements. Estimated duration: {self.calculate_time_left(variations, variations[0])}")

        if answers["generate_model_json"] and not resume_at:
            try:
                standby_power = self.measure_standby_power()
            except PowerMeterError as error:
                _LOGGER.error(f"Aborting: {error}")
                return
            
            self.write_model_json(
                directory=export_directory,
                standby_power=standby_power,
                name=answers["model_name"],
                measure_device=answers["measure_device"],
            )

        with open(csv_file_path, file_write_mode, newline="") as csv_file:
            csv_writer = CsvWriter(csv_file, self.color_mode, write_header_row)

            if resume_at is None:
                self.light_controller.change_light_state(MODE_BRIGHTNESS, on=True, bri=1)

            # Initially wait longer so the smartplug can settle
            _LOGGER.info(f"Start taking measurements for color mode: {self.color_mode}")
            _LOGGER.info(f"Waiting {SLEEP_INITIAL} seconds...")
            time.sleep(SLEEP_INITIAL)

            previous_variation = None
            for count, variation in enumerate(variations):
                if count % 10 == 0:
                    time_left = self.calculate_time_left(variations, variation, count)
                    progress_percentage = round(count / num_variations * 100)
                    _LOGGER.info(f"Progress: {progress_percentage}%, Estimated time left: {time_left}")
                _LOGGER.info(f"Changing light to: {variation}")
                variation_start_time = time.time()
                self.light_controller.change_light_state(
                    self.color_mode, on=True, **asdict(variation)
                )

                if previous_variation and isinstance(variation, ColorTempVariation) and variation.ct < previous_variation.ct:
                    _LOGGER.info("Extra waiting for significant CT change...")
                    time.sleep(SLEEP_TIME_CT)

                if previous_variation and isinstance(variation, HsVariation) and variation.sat < previous_variation.sat:
                    _LOGGER.info("Extra waiting for significant SAT change...")
                    time.sleep(SLEEP_TIME_SAT)

                if previous_variation and isinstance(variation, HsVariation) and variation.hue < previous_variation.hue:
                    _LOGGER.info("Extra waiting for significant HUE change...")
                    time.sleep(SLEEP_TIME_HUE)

                previous_variation = variation
                time.sleep(SLEEP_TIME)
                try:
                    power = self.take_power_measurement(variation_start_time)
                except ZeroReadingError as error:
                    self.num_0_readings += 1
                    _LOGGER.warning(f"Discarding measurement: {error}")
                    if self.num_0_readings > MAX_ALLOWED_0_READINGS:
                        _LOGGER.error("Aborting measurement session. Received to much 0 readings")
                        return
                    continue
                except PowerMeterError as error:
                    _LOGGER.error(f"Aborting: {error}")
                    return
                _LOGGER.info(f"Measured power: {power}")
                csv_writer.write_measurement(variation, power)

            csv_file.close()
            _LOGGER.info(f"Hooray! measurements finished. Exported CSV file {csv_file_path}")

        if bool(answers.get("gzip", True)):
            self.gzip_csv(csv_file_path)

    def should_resume(self, csv_file_path: str) -> bool:
        """Check whether we are able to resume a previous measurement session"""
        if not os.path.exists(csv_file_path):
            return False
        
        size = os.path.getsize(csv_file_path) 
        if size == 0:
            return False
        
        with open(csv_file_path, "r") as csv_file:
            rows = csv.reader(csv_file)
            if len(list(rows)) == 1:
                return False

        return inquirer.confirm(
            message="CSV File already exists. Do you want to resume measurements?",
            default=True
        )


    def get_resume_variation(self, csv_file_path: str) -> Variation:
        """Determine the variation where we have to resume the measurements"""
        with open(csv_file_path, "r") as csv_file:
            rows = csv.reader(csv_file)
            last_row = list(rows)[-1]

        if self.color_mode == MODE_BRIGHTNESS:
            return Variation(bri=int(last_row[0]))

        if self.color_mode == MODE_COLOR_TEMP:
            return ColorTempVariation(bri=int(last_row[0]), ct=int(last_row[1]))

        if self.color_mode == MODE_HS:
            return HsVariation(bri=int(last_row[0]), hue=int(last_row[1]), sat=int(last_row[2]))

        raise Exception(f"Color mode {self.color_mode} not supported")


    def take_power_measurement(self, start_timestamp: float, retry_count: int=0) -> float:
        """Request a power reading from the configured power meter"""
        measurements = []
        # Take multiple samples to reduce noise
        for i in range(1, SAMPLE_COUNT + 1):
            _LOGGER.debug(f"Taking sample {i}")
            error = None
            try:
                measurement = self.power_meter.get_power()
                updated_at = dt.fromtimestamp(measurement.updated).strftime("%d-%m-%Y, %H:%M:%S")
                _LOGGER.debug(f"Measurement received (update_time={updated_at})")
            except PowerMeterError as err:
                error = err

            # Check if measurement is not outdated
            if measurement.updated < start_timestamp:
                error = OutdatedMeasurementError(f"Power measurement is outdated. Aborting after {MAX_RETRIES} retries")

            # Check if we not have a 0 measurument
            if measurement.power == 0:
                error = ZeroReadingError("0 watt was read from the power meter")

            if error:
                # Prevent endless recursion. Throw error when max retries is reached
                if retry_count == MAX_RETRIES:
                    raise error
                retry_count += 1
                time.sleep(SLEEP_TIME)
                self.take_power_measurement(start_timestamp, retry_count)

            measurements.append(measurement.power)
            if SAMPLE_COUNT > 1:
                time.sleep(SLEEP_TIME_SAMPLE)

        value = sum(measurements) / len(measurements) / self.num_lights
        if self.is_dummy_load_connected:
            value = value - self.dummy_load_value

        return round(value, 2)

    def gzip_csv(self, csv_file_path: str):
        """Gzip the CSV file"""
        with open(csv_file_path, "rb") as csv_file:
            with gzip.open(f"{csv_file_path}.gz", "wb") as gzip_file:
                shutil.copyfileobj(csv_file, gzip_file)


    def measure_standby_power(self) -> float:
        """Measures the standby power (when the light is OFF)"""
        self.light_controller.change_light_state(MODE_BRIGHTNESS, on=False)
        start_time = time.time()
        _LOGGER.info(f"Measuring standby power. Waiting for {SLEEP_STANDBY} seconds...")
        time.sleep(SLEEP_STANDBY)
        try:
            return self.take_power_measurement(start_time)
        except ZeroReadingError:
            _LOGGER.error("Measured 0 watt as standby usage, continuing now, but you probably need to have a look into measuring multiple lights at the same time or using a dummy load.")
            return 0

    def get_variations(self, color_mode: str, resume_at: Optional[Variation] = None) -> Iterator[Variation]:
        """Get all the light settings where the measure script needs to cycle through"""
        if color_mode == MODE_HS:
            variations = self.get_hs_variations()
        elif color_mode == MODE_COLOR_TEMP:
            variations = self.get_ct_variations()
        else:
            variations = self.get_brightness_variations()
        
        if resume_at:
            include_variation = False
            for variation in variations:
                if include_variation:
                    yield variation

                # Current variation is the one we need to resume at.
                # Set include_variation flag so it every variation from now on will be yielded next iteration
                if variation == resume_at:
                    include_variation = True
        else:
            yield from variations

    def get_ct_variations(self) -> Iterator[ColorTempVariation]:
        """Get color_temp variations"""
        min_mired = round(self.light_info.min_mired)
        max_mired = round(self.light_info.max_mired)
        for bri in self.inclusive_range(MIN_BRIGHTNESS, MAX_BRIGHTNESS, CT_BRI_STEPS):
            for mired in self.inclusive_range(min_mired, max_mired, CT_MIRED_STEPS):
                yield ColorTempVariation(bri=bri, ct=mired)

    def get_hs_variations(self) -> Iterator[HsVariation]:
        """Get hue/sat variations"""
        for bri in self.inclusive_range(MIN_BRIGHTNESS, MAX_BRIGHTNESS, HS_BRI_STEPS):
            for sat in self.inclusive_range(MIN_SAT, MAX_SAT, HS_SAT_STEPS):
                for hue in self.inclusive_range(MIN_HUE, MAX_HUE, HS_HUE_STEPS):
                    yield HsVariation(bri=bri, hue=hue, sat=sat)

    def get_brightness_variations(self) -> Iterator[Variation]:
        """Get brightness variations"""
        for bri in self.inclusive_range(MIN_BRIGHTNESS, MAX_BRIGHTNESS, BRI_BRI_STEPS):
            yield Variation(bri=bri)

    def inclusive_range(self, start: int, end: int, step: int) -> Iterator[int]:
        """Get an iterator including the min and max, with steps in between"""
        i = start
        while i < end:
            yield i
            i += step
        yield end

    def calculate_time_left(self, variations: list[Variation], current_variation: Variation = None, progress: int = 0) -> str:
        """Try to guess the remaining time left. This will not account for measuring errors / retries obviously"""
        num_variations_left = len(variations) - progress

        # Account estimated seconds for the light_controller and power_meter to process
        estimated_step_delay = 0.15

        time_left = 0
        if progress == 0:
            time_left += SLEEP_STANDBY + SLEEP_INITIAL
        time_left += num_variations_left * (SLEEP_TIME + estimated_step_delay)
        if SAMPLE_COUNT > 1:
            time_left += num_variations_left * SAMPLE_COUNT * (SLEEP_TIME_SAMPLE + estimated_step_delay)

        if isinstance(current_variation, HsVariation):
            sat_steps_left = round((MAX_BRIGHTNESS - current_variation.bri) / HS_BRI_STEPS) - 1
            time_left += sat_steps_left * SLEEP_TIME_SAT
            hue_steps_left = round(MAX_HUE / HS_HUE_STEPS * sat_steps_left)
            time_left += hue_steps_left * SLEEP_TIME_HUE

        if isinstance(current_variation, ColorTempVariation):
            ct_steps_left = round((MAX_BRIGHTNESS - current_variation.bri) / CT_BRI_STEPS) - 1
            time_left += ct_steps_left * SLEEP_TIME_CT

        if time_left > 3600:
            formatted_time = f"{round(time_left / 3600, 1)}h"
        elif time_left > 60:
            formatted_time = f"{round(time_left / 60, 1)}m"
        else:
            formatted_time = f"{time_left}s"

        return formatted_time

    def write_model_json(
        self, directory: str, standby_power: float, name: str, measure_device: str
    ):
        """Write model.json manifest file"""
        json_data = json.dumps(
            {
                "measure_device": measure_device,
                "measure_method": "script",
                "measure_description": "Measured with utils/measure script",
                "measure_settings": {
                    "VERSION": _VERSION,
                    "SAMPLE_COUNT": SAMPLE_COUNT,
                    "SLEEP_TIME": SLEEP_TIME
                },
                "name": name,
                "standby_power": standby_power,
                "supported_modes": ["lut"],
            },
            indent=4,
            sort_keys=True,
        )
        json_file = open(os.path.join(directory, "model.json"), "w")
        json_file.write(json_data)
        json_file.close()
    

    def get_questions(self) -> list[Question]:
        """Build list of questions to ask"""
        questions = [
            inquirer.List(
                name="color_mode",
                message="Select the color mode",
                choices=[MODE_HS, MODE_COLOR_TEMP, MODE_BRIGHTNESS],
                default=MODE_HS
            ),
            inquirer.Confirm(
                name="generate_model_json",
                message="Do you want to generate model.json?",
                default=True
            ),
            inquirer.Text(
                name="model_name",
                message="Specify the full light model name",
                ignore=lambda answers: not answers.get("generate_model_json"),
                validate=validate_required,
            ),
            inquirer.Text(
                name="measure_device",
                message="Which powermeter (manufacturer, model) do you use to take the measurement?",
                ignore=lambda answers: not answers.get("generate_model_json"),
                validate=validate_required,
            ),
            inquirer.Confirm(
                name="gzip",
                message="Do you want to gzip CSV files?",
                default=True
            ),
            inquirer.Confirm(
                name="dummy_load",
                message="Did you connect a dummy load? This can help to be able to measure standby power and low brightness levels correctly",
                default=False
            ),
            inquirer.Confirm(
                name="multiple_lights",
                message="Are you measuring multiple lights. In some situations it helps to connect multiple lights to be able to measure low currents.",
                default=False
            ),
            inquirer.Text(
                name="num_lights",
                message="How many lights are you measuring?",
                ignore=lambda answers: not answers.get("multiple_lights"),
                validate=lambda _, current: re.match('\d+', current),
            ),
        ]

        questions.extend(self.light_controller.get_questions())
        questions.extend(self.power_meter.get_questions())

        return questions

    
    def ask_questions(self) -> dict[str, Any]:
        """Ask question and return a dictionary with the answers"""
        all_questions = self.get_questions()

        #Only ask questions which answers are not predefined in .env file
        questions_to_ask = [question for question in all_questions if not config_key_exists(str(question.name).upper())]

        predefined_answers = {}
        for question in all_questions:
            question_name = str(question.name)
            env_var = question_name.upper()
            if config_key_exists(env_var):
                conf_value = config(env_var)
                if isinstance(question, inquirer.Confirm):
                    conf_value = bool(str_to_bool(conf_value))
                predefined_answers[question_name] = conf_value

        answers = inquirer.prompt(questions_to_ask, answers=predefined_answers)

        _LOGGER.debug("Answers: %s", answers)

        return answers
    
    def get_dummy_load_value(self) -> float:
        """Get the previously measured dummy load value"""

        #dummy_load_file = Path(".persistent/dummy_load").resolve()
        dummy_load_file = os.path.join(Path(__file__).parent.absolute(), ".persistent/dummy_load")
        if not os.path.exists(dummy_load_file):
            return self.measure_dummy_load(dummy_load_file)

        with open(dummy_load_file, "r") as f:
            return float(f.read())
    
    def measure_dummy_load(self, file_path: str) -> float:
        """Measure the dummy load and persist the value for future measurement session"""
        input("Only connect your dummy load to your smart plug, not the light! Press enter to start measuring the dummy load..")
        values = []
        for i in range(1):
            result = self.power_meter.get_power()
            values.append(result.power)
            _LOGGER.info(f"Dummy load watt: {result.power}")
            time.sleep(SLEEP_TIME)
        average = sum(values) / len(values)

        with open(file_path, "w") as f:
            f.write(str(average))

        input("Connect your light now and press enter to start measuring..")
        return average

class CsvWriter:
    def __init__(self, csv_file: TextIOWrapper, color_mode: str, add_header: bool):
        self.csv_file = csv_file
        self.writer = csv.writer(csv_file)
        self.rows_written = 0
        if add_header:
            header_row = CSV_HEADERS[color_mode]
            if CSV_ADD_DATETIME_COLUMN:
                header_row.append("time")
            self.writer.writerow(header_row)
    
    def write_measurement(self, variation: Variation, power: float):
        """Write row with measurement to the CSV"""
        row = variation.to_csv_row()
        row.append(power)
        if CSV_ADD_DATETIME_COLUMN:
            row.append(dt.now().strftime("%Y%m%d%H%M%S"))
        self.writer.writerow(row)
        self.rows_written += 1
        if self.rows_written % CSV_WRITE_BUFFER == 1:
            self.csv_file.flush()
            _LOGGER.debug("Flushing CSV buffer")


def config_key_exists(key: str) -> bool:
    """Check whether a certain configuration exists in dot env file"""
    try:
        config(key)
        return True
    except UndefinedValueError:
        return False

def validate_required(_, val):
    """Validation function for the inquirer question, checks if the input has a not empty value"""
    if len(val) == 0:
        raise ValidationError("", reason="This question cannot be empty, please put in a value")
    return True

def str_to_bool(value: Any) -> bool:
    """Return whether the provided string (or any value really) represents true."""
    if not value:
        return False
    return str(value).lower() in ("y", "yes", "t", "true", "on", "1")

@dataclass(frozen=True)
class Variation:
    bri: int

    def to_csv_row(self) -> list:
        return [self.bri]

@dataclass(frozen=True)
class HsVariation(Variation):
    hue: int
    sat: int

    def to_csv_row(self) -> list:
        return [self.bri, self.hue, self.sat]
    
    def is_hue_changed(self, other_variation: HsVariation):
        return self.hue != other_variation.hue
    
    def is_sat_changed(self, other_variation: HsVariation):
        return self.sat != other_variation.sat

@dataclass(frozen=True)
class ColorTempVariation(Variation):
    ct: int

    def to_csv_row(self) -> list:
        return [self.bri, self.ct]
    
    def is_ct_changed(self, other_variation: ColorTempVariation):
        return self.ct != other_variation.ct

class LightControllerFactory:
    def hass(self):
        return HassLightController(HASS_URL, HASS_TOKEN)

    def hue(self):
        return HueLightController(HUE_BRIDGE_IP)

    def dummy(self):
        return LightController()

    def create(self) -> LightController:
        """Create the light controller object"""
        factories = {
            LIGHT_CONTROLLER_DUMMY: self.dummy,
            LIGHT_CONTROLLER_HUE: self.hue,
            LIGHT_CONTROLLER_HASS: self.hass
        }
        factory = factories.get(SELECTED_LIGHT_CONTROLLER)
        if factory is None:
            raise Exception(f"Could not find a factory for {SELECTED_LIGHT_CONTROLLER}")

        _LOGGER.info(f"Selected Light controller: {SELECTED_LIGHT_CONTROLLER}")
        return factory()


class PowerMeterFactory:
    def dummy(self):
        return DummyPowerMeter()

    def hass(self):
        return HassPowerMeter(HASS_URL, HASS_TOKEN)

    def kasa(self):
        return KasaPowerMeter(KASA_DEVICE_IP)
    
    def manual(self):
        return ManualPowerMeter()
    
    def ocr(self):
        return OcrPowerMeter()

    def shelly(self):
        return ShellyPowerMeter(SHELLY_IP, SHELLY_TIMEOUT)

    def tasmota(self):
        return TasmotaPowerMeter(TASMOTA_DEVICE_IP)

    def tuya(self):
        return TuyaPowerMeter(
            TUYA_DEVICE_ID, TUYA_DEVICE_IP, TUYA_DEVICE_KEY, TUYA_DEVICE_VERSION
        )

    def create(self) -> PowerMeter:
        """Create the power meter object"""
        factories = {
            POWER_METER_HASS: self.hass,
            POWER_METER_KASA: self.kasa,
            POWER_METER_MANUAL: self.manual,
            POWER_METER_OCR: self.ocr,
            POWER_METER_SHELLY: self.shelly,
            POWER_METER_TASMOTA: self.tasmota,
            POWER_METER_TUYA: self.tuya,
            POWER_METER_DUMMY: self.dummy
        }
        factory = factories.get(SELECTED_POWER_METER)
        if factory is None:
            raise PowerMeterError(f"Could not find a factory for {SELECTED_POWER_METER}")

        _LOGGER.info(f"Selected powermeter: {SELECTED_POWER_METER}")
        return factory()

def main():
    print(f"Powercalc measure: {_VERSION}\n")

    light_controller_factory = LightControllerFactory()
    power_meter_factory = PowerMeterFactory()

    try:
        power_meter = power_meter_factory.create()
        light_controller = light_controller_factory.create()
    
        measure = Measure(light_controller, power_meter)
        measure.start()
    except (PowerMeterError, LightControllerError) as e:
        _LOGGER.error(f"Aborting: {e}")
        exit(1)

if __name__ == "__main__":
    main()

utils/measure/__init__.py METASEP

.github/scripts/update_hacs_manifest.py METASEP
"""Update the manifest file."""
import json
import os
import sys


def update_manifest():
    """Update the manifest file."""
    version = "0.0.0"
    for index, value in enumerate(sys.argv):
        if value in ["--version", "-V"]:
            version = sys.argv[index + 1]

    with open(
        f"{os.getcwd()}/custom_components/powercalc/manifest.json"
    ) as manifestfile:
        manifest = json.load(manifestfile)

    manifest["version"] = version

    with open(
        f"{os.getcwd()}/custom_components/powercalc/manifest.json", "w"
    ) as manifestfile:
        manifestfile.write(json.dumps(manifest, indent=4, sort_keys=True))


update_manifest()

custom_components/powercalc/data/yeelight/YLDP13YL/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.20.3:docker"
    },
    "name": "Yeelight LED Bulb 1S",
    "standby_power": 1.22,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/yeelight/YLDP13YL/hs.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDP13YL/color_temp.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDP06YL/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa KP115",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 10,
        "VERSION": "v0.19.7"
    },
    "name": "Yeelight Smart LED Bulb II Color",
    "standby_power": 1.07,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/yeelight/YLDP06YL/hs.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDP06YL/color_temp.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDP01YL/model.json METASEP
{
    "name": "Yeelight LED 600lm 4000K 8W WiFi",
    "standby_power": 1.1,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "RRPM02 (Model); 4897037690801 (Barcode); Reduction Revolution Plug-in Power Meter",
    "measure_description": "No comments"
}

custom_components/powercalc/data/yeelight/YLDP01YL/brightness.csv.gz METASEP

custom_components/powercalc/data/yeelight/YLDL01YL/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 1,
        "VERSION": "master"
    },
    "name": "Yeelight YLDL01YL Downlight",
    "standby_power": 0.72,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/yeelight/YLDD05YL/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.20.3:docker"
    },
    "name": "Yeelight LED Light Strip 1S",
    "standby_power": 0.69,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/wiz/9290023839/model.json METASEP
{
    "measure_description": "Measured 1 bulb with utils/measure script using an additional dummy load (IKEA LED1457C3 E14 bulb measured at 2.61 W)",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "master"
    },
    "name": "WiZ Globe G95 E27 Color (type SHRGB1C, model 9290023839)",
    "standby_power": 0.96,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/wiz/9290023839/hs.csv.gz METASEP

custom_components/powercalc/data/wiz/9290023839/color_temp.csv.gz METASEP

custom_components/powercalc/data/tuya/NO66-ZB/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.20.3:docker"
    },
    "name": "Tuya Zigbee LED Smart Strip DC12V RGBCCT 60led/m",
    "standby_power": 1.05,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/sonoff/B02BA60/model.json METASEP
{
    "measure_device": "Sonoff DualR3",
    "measure_description": "Took several readings on every 10% of the lamp and did a linear regression to estimate the correct consumptions. Standby power is assumed",
    "measure_method": "script",
    "name": "Sonoff B02 Light with dimmer and temperature",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/sonoff/B02BA60/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LWW001/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "master"
    },
    "name": "Hue white A60 bulb E27",
    "standby_power": 0.3,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LWW001/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWV002/model.json METASEP
{
   "name":"Hue Single Filament Bulb ST19 E26",
   "standby_power":0.5,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"Gosund SP111 on Tasmota 9.5.0",
   "measure_description":"Measure Script for Tasmota",
   "measure_method":"script",
   "linked_lut":"signify/LWV001"
}
custom_components/powercalc/data/signify/LWV001/model.json METASEP
{
   "name":"Hue White Filament Bulb ST64 E27",
   "standby_power":0.5,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"Gosund SP111 on Tasmota 9.5.0",
   "measure_description":"Measure Script for Tasmota",
   "measure_method":"script"
}
custom_components/powercalc/data/signify/LWV001/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWU001/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "master"
    },
    "name": "Hue P45 light bulb",
    "standby_power": 0.2,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LWU001/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWO003/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 10,
        "SLEEP_TIME": 10
    },
    "name": "Hue White Filament Bulb G125 E27 w/ BT",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LWO003/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWO002/model.json METASEP
{
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_description": "Measured with utils/measure, SAMPLE_COUNT=3",
    "name": "Hue White G25 E26 Edison Filament Globe Bluetooth",
    "standby_power": 0.35,
    "supported_modes": [
        "lut"
    ],
    "linked_lut": "signify/LWO001"
}

custom_components/powercalc/data/signify/LWO001/model.json METASEP
{
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_description": "Measured with utils/measure, SAMPLE_COUNT=3",
    "name": "Hue White Filament Bulb G93 E27 w/ BT",
    "standby_power": 0.35,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LWO001/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWG004/model.json METASEP
{
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "name": "Hue White GU10 Bluetooth",
    "standby_power": 0.25,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LWG004/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWG001/model.json METASEP
{
   "name":"Hue White GU10",
   "standby_power":0.4,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"Gosund SP111 on Tasmota 9.5.0",
   "measure_description":"Measure Script for Tasmota",
   "measure_method":"script"
}
custom_components/powercalc/data/signify/LWG001/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWF001/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "master"
    },
    "name": "Hue white A60 bulb E27",
    "standby_power": 0.3,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LWF001/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWE002/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "TP-Link Tapo P110 (via Home Assistant)",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 25,
        "VERSION": "v0.19.7:docker"
    },
    "name": "Hue White E14 SES Candle 470lm w/ BT (LWE002)",
    "standby_power": 0.25,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LWE002/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWB014/model.json METASEP
{
  "measure_description": "Measured with utils/measure script",
  "measure_device": "TPLink Smart Wi-Fi Plug Mini",
  "measure_method": "script",
  "measure_settings": {
    "SAMPLE_COUNT": 1,
    "SLEEP_TIME": 2,
    "VERSION": "v0.20.1:docker"
  },
  "name": "Philips Hue 840 Lumen White A19",
  "standby_power": 0.34,
  "supported_modes": ["lut"]
}

custom_components/powercalc/data/signify/LWB014/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWB010/model.json METASEP
{
    "name": "Hue White A60 E27 806lm",
    "standby_power": 0.39,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
    "measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
}

custom_components/powercalc/data/signify/LWB010/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWB006/model.json METASEP
{
    "name": "Hue White A60 E27 800lm",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "RRPM02 (Model); 4897037690801 (Barcode); Reduction Revolution Plug-in Power Meter",
    "measure_description": "Was not able to measure the standby power, the value is assumed. Two bulbs were measured and the higher reading at each dim setting (0-100) was used"
}

custom_components/powercalc/data/signify/LWB006/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWB004/model.json METASEP
{
    "name": "Hue Lux E27 750lm",
    "standby_power": 0.29,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
    "measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
}

custom_components/powercalc/data/signify/LWB004/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWA017/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plus 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 5,
        "VERSION": "v0.17.0:docker"
    },
    "name": "Hue white A60 bulb E27 1050lm with Bluetooth",
    "standby_power": 0.0,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LWA017/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWA009/model.json METASEP
{
    "name": "Hue White 1600 A67 E27 1600lm",
    "standby_power": 0.41,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured twice with script made by bramstroker using 9 sec pauses and partially verified manual"
}

custom_components/powercalc/data/signify/LWA009/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWA008/model.json METASEP
{
    "name": "Hue White Bulb A21 E26 1600lm with Bluetooth",
    "standby_power": 0.41,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured twice with script made by bramstroker using 9 sec pauses and partially verified manual",
    "linked_lut": "signify/LWA009"
}

custom_components/powercalc/data/signify/LWA005/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Sonoff, S31",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 7,
        "SLEEP_TIME": 15,
        "VERSION": "v0.19.15:docker"
    },
    "name": "Hue Filament E26",
    "standby_power": 0.11,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LWA005/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWA004/model.json METASEP
{
    "name": "Hue White Filament Bulb A60 E27",
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly plug S",
    "measure_description": "Used script utils/measure_shelly.py from repository",
    "standby_power": 0.26
}

custom_components/powercalc/data/signify/LWA004/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LWA003/model.json METASEP
{
    "name": "Hue White A19 E26 Bluetooth",
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly PM",
    "standby_power": 0.4,
    "measure_description": "Used script utils/measure_shelly.py from repository, estimated standby",
    "linked_lut": "signify/LWA001"
}

custom_components/powercalc/data/signify/LWA001/model.json METASEP
{
    "name": "Hue White Ambiance Bulb A60 E27",
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly PM",
    "standby_power": 0.4,
    "measure_description": "Used script utils/measure_shelly.py from repository, estimated standby"
}

custom_components/powercalc/data/signify/LWA001/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LTW017/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 3,
        "SLEEP_TIME": 1,
        "VERSION": "v0.12.2"
    },
    "name": "Hue Adore Bathroom Mirror Light",
    "standby_power": 0.42,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LTW017/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTW013/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plus 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 5,
        "VERSION": "v0.17.0:docker"
    },
    "name": "Hue white ambiance GU10",
    "standby_power": 0.3,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LTW013/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTW012/model.json METASEP
{
    "name": " Hue White Ambiance Candle E14",
    "supported_modes": [
        "lut"
    ],
    "standby_power": 0.3,
    "measure_method": "script",
    "measure_device": "Shelly PM",
    "measure_description": "Used script utils/measure_shelly.py from repository, standby_power estimated"
}

custom_components/powercalc/data/signify/LTW012/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTW010/model.json METASEP
{
    "name": "Hue White Ambiance A60 E27 806lm 9290012001A",
    "standby_power": 0.39,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured with script made by bramstroker and partially verified manual"
}
custom_components/powercalc/data/signify/LTW010/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTW001/model.json METASEP
{
    "name": "Hue White Ambiance A19",
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly plug S",
    "measure_description": "Used script utils/measure_shelly.py from repository. Standby power is guessed",
    "standby_power": 0.4
}

custom_components/powercalc/data/signify/LTW001/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTP008/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.18.4:docker"
    },
    "name": "Hue Being Pendant Light",
    "standby_power": 0.38,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LTP008/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTP003/model.json METASEP
{
   "name":"Hue Fair Pendant",
   "standby_power":0.4,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"Gosund SP111 on Tasmota 9.5.0",
   "measure_description":"Measure Script for Tasmota",
   "measure_method":"script"
}

custom_components/powercalc/data/signify/LTP003/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTO001/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly plug s",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.18.4:docker"
    },
    "name": "Hue G93 E27 Filament Globe Bulb",
    "standby_power": 0.17,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LTO001/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTG002/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2
    },
    "name": "Hue White Ambiance GU10 w/ BT",
    "standby_power": 0.3,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LTG002/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTE002/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Gosuna Smart Plug UP111 (via tuya)",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.15:docker"
    },
    "name": "Hue White Ambiance E14 w/ BT",
    "standby_power": 0.2,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LTE002/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTC012/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 3,
        "SLEEP_TIME": 1,
        "VERSION": "v0.12.2"
    },
    "name": "Hue Struana Ceiling Light",
    "standby_power": 0.35,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LTC012/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTC002/model.json METASEP
{
   "name":"Hue Fair Ceiling Lamp",
   "standby_power":0.4,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"Gosund SP111 on Tasmota 9.5.0",
   "measure_description":"Measure Script for Tasmota",
   "measure_method":"script"
}

custom_components/powercalc/data/signify/LTC002/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTC001/model.json METASEP
{
    "name": "Hue Being Ceiling Light",
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly plug S",
    "measure_description": "Used script utils/measure_shelly.py from repository. standby_power is guessed",
    "standby_power": 0.35
}

custom_components/powercalc/data/signify/LTC001/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTA011/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa KP115",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 10,
        "VERSION": "v0.19.7"
    },
    "name": "Hue White Ambiance 1600 E27 w/ BT",
    "standby_power": 0.21,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LTA011/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTA009/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Gosuna Smart Plug UP111 (via tuya)",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.15:docker"
    },
    "name": "Hue White Ambiance 1100 E27 Bluetooth",
    "standby_power": 0.2,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LTA009/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTA004/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Fibargroup, FGWP102",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 5,
        "VERSION": "v0.16.0:docker"
    },
    "name": "Philips Hue White Ambiance 800 E27 Bluetooth",
    "standby_power": 0.3,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LTA004/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTA003/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Sonoff, S31",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 3,
        "SLEEP_TIME": 15,
        "VERSION": "v0.19.15:docker"
    },
    "name": "Hue White Ambiance E26",
    "standby_power": 0.29,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LTA003/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LTA001/model.json METASEP
{
    "name": "Hue White Ambiance E27",
    "standby_power": 0.29,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "TP-Link HS110",
    "measure_description": "Measured with script made by smonesi (steps: bri 30/mired 30) and linearly interpolated to provide more data"
}

custom_components/powercalc/data/signify/LTA001/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LST004/model.json METASEP
{
   "name":"Hue White and Color Ambiance LED Outdoor Lightstrip (5m)",
   "standby_power":0.4,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"Gosund SP111 on Tasmota 9.5.0",
   "measure_description":"Measure Script for Tasmota",
   "measure_method":"script"
}
custom_components/powercalc/data/signify/LST004/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LST004/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LST002/model.json METASEP
{
   "name":"Hue LightStrip Plus (2m)",
   "standby_power":0.18,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"FRITZ!DECT 200",
   "measure_description":"Measured by custom script with all lightstrip extensions detached. Measured standby power with ELV Energy Master Profi 2",
   "measure_method":"script"
}

custom_components/powercalc/data/signify/LST002/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LST002/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LST001/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2
    },
    "name": "Hue LightStrip",
    "standby_power": 0.41,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LST001/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LLC020/model.json METASEP
{
    "name": "Hue Go",
    "standby_power": 0.290,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "TP-Link HS110",
    "measure_description": "Measured with script made by smonesi (steps: bri 20/sat 40/hue 2000/mired 30) and linearly interpolated to provide more data"
}

custom_components/powercalc/data/signify/LLC020/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LLC020/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LLC012/model.json METASEP
{
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "name": "Hue Bloom Table Lamp",
    "standby_power": 0.3,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LLC012/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LLC011/model.json METASEP
{
    "name": "Living Colours Bloom 7099760PH",
    "standby_power": 0.29,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured with script made by bramstroker and partially verified manual"
}
custom_components/powercalc/data/signify/LLC011/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LLC010/model.json METASEP
{
    "name": "Hue Iris",
    "standby_power": 0.40,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "TP-Link HS110",
    "measure_description": "Measured with script made by smonesi (steps: bri 50/sat 60/hue 2000) and linearly interpolated to provide more data"
}

custom_components/powercalc/data/signify/LLC010/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LLC006/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.14.1"
    },
    "name": "LivingColors Iris Table Lamp Gen3",
    "standby_power": 0.37,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LLC006/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LLC006/brightness.csv.gz METASEP

custom_components/powercalc/data/signify/LLC001/model.json METASEP
{
    "name": "Philips Living Color Iris",
    "standby_power": 0.500,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "TP-Link HS110",
    "measure_description": "Measured with script made by smonesi (steps: bri 50/sat 60/hue 2000) and linearly interpolated to provide more data"
}

custom_components/powercalc/data/signify/LLC001/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT026/model.json METASEP
{
    "name": "Hue Go 2.0",
    "standby_power": 0.49,
    "measure_method": "script",
    "measure_device": "TP-Link HS110",
    "measure_description": "Measured with measure script made by smonesi (SAMPLE_COUNT=6, steps: bri 40/sat 40/mired 30/hue 2000) and linearly interpolated to provide more data",
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LCT026/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT026/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT024/model.json METASEP
{
    "name": "Hue Play",
    "standby_power": 0.100,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "TP-Link HS110",
    "measure_description": "Measured with script made by smonesi (steps: bri 20/sat 40/mired 30/hue 2000) and linearly interpolated to provide more data"
}

custom_components/powercalc/data/signify/LCT024/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT024/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT016/model.json METASEP
{
  "measure_description": "Measured with utils/measure script",
  "measure_device": "TPLink Smart Wi-Fi Plug Mini",
  "measure_method": "script",
  "measure_settings": {
    "SAMPLE_COUNT": 1,
    "SLEEP_TIME": 2,
    "VERSION": "v0.20.1:docker"
  },
  "name": "Philips Hue 800 Lumen White and Color A19",
  "standby_power": 0.34,
  "supported_modes": ["lut"]
}

custom_components/powercalc/data/signify/LCT016/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT016/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT015/model.json METASEP
{
    "name": "Hue White and Color Ambiance A19 E26/E27 (Gen 4)",
    "standby_power": 0.47,
    "measure_device": "unknown",
    "measure_method": "script",
    "measure_description": "",
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LCT015/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT015/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT014/model.json METASEP
{
  "measure_description": "Measured with utils/measure script",
  "measure_device": "TPLink Smart Wi-Fi Plug Mini",
  "measure_method": "script",
  "measure_settings": {
    "SAMPLE_COUNT": 1,
    "SLEEP_TIME": 2,
    "VERSION": "v0.20.1:docker"
  },
  "name": "Philips Hue 800 Lumen White and Color A19",
  "standby_power": 0.34,
  "supported_modes": ["lut"]
}

custom_components/powercalc/data/signify/LCT014/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT014/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT012/model.json METASEP
{
    "name": "Hue White and Color Ambiance Candle E12",
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly plug S",
    "measure_description": "Used script utils/measure_shelly.py from repository",
    "standby_power": 0.35
}

custom_components/powercalc/data/signify/LCT012/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT012/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT010/model.json METASEP
{
    "name": "Hue White and Color Ambiance A19 E26 (Gen 3)",
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly plug S",
    "measure_description": "Used script utils/measure_shelly.py from repository",
    "standby_power": 0.35
}

custom_components/powercalc/data/signify/LCT010/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT010/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT007/model.json METASEP
{
    "measure_device": "Nous A1, Tuya w/ TASMOTA 9.5.0",
    "measure_method": "script",
    "measure_description": "Used script in utils/measure from repository",
    "measure_setting": {
        "SAMPLE_COUNT": 10,
        "SLEEP_TIME": 2,
        "SLEEP_TIME_HUE": 5,
        "SLEEP_TIME_SAT": 10,
        "SLEEP_TIME_CT": 10
    },
    "name": "Hue White and Color E27 Gen2 (2015)",
    "standby_power": 0.33,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LCT007/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT007/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT003/model.json METASEP
{
    "name": "Hue White and Color Ambiance Spot GU10",
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly plug S",
    "measure_description": "Used script utils/measure_shelly.py from repository",
    "standby_power": 0.25
}

custom_components/powercalc/data/signify/LCT003/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT003/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCT001/model.json METASEP
{
    "name": "Philips Hue White and Color Ambience (1 gen)",
    "supported_modes": [
        "lut"
    ],
    "standby_power": 0.22,
    "measure_method": "script",
    "measure_device": "Shelly PM1",
    "measure_description": "Used HASS based script"
}

custom_components/powercalc/data/signify/LCT001/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCT001/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCS001/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2
    },
    "name": "Hue Lily Outdoor Spot Light RGBCCT",
    "standby_power": 0.36,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LCS001/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCS001/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCL001/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 5,
        "VERSION": "v0.18.4:docker"
    },
    "name": "Hue white and color ambiance LightStrip plus",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LCL001/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCL001/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCG002/model.json METASEP
{
    "name":"Hue White and Color Ambiance GU10 BT",
    "standby_power":0.4,
    "supported_modes":[
       "lut"
    ],
    "measure_device":"Gosund SP111 on Tasmota 9.5.0",
    "measure_description":"Measure Script for Tasmota",
    "measure_method":"script"
 }
custom_components/powercalc/data/signify/LCG002/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCG002/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCF002/model.json METASEP
{
   "name":"Hue Calla Outdoor Pedestal",
   "standby_power":0.4,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"Gosund SP111 on Tasmota 9.5.0",
   "measure_description":"Measure Script for Tasmota",
   "measure_method":"script"
}
custom_components/powercalc/data/signify/LCF002/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCF002/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCE002/model.json METASEP
{
    "name": "Hue White and Color Ambiance E14 Candle w/ BT",
    "standby_power": 0.28,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "AVM FRITZ!DECT 200",
    "measure_description": "Measured with own custom script"
}

custom_components/powercalc/data/signify/LCE002/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCE002/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCA009/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "TPLink KP125",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.19.12:docker"
    },
    "name": "Hue color lamp (LCA009)",
    "standby_power": 0.19,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LCA009/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCA009/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCA008/model.json METASEP
{
    "measure_description": "Measured 1 bulb with utils/measure script using an additional dummy load (IKEA LED1457C3 E14 bulb measured at 2.61 W)",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "master"
    },
    "name": "Hue White and Color Ambiance E27 1600lm",
    "standby_power": 0.27,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/signify/LCA008/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCA008/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCA007/model.json METASEP
{
    "measure_device": "Nous A1 with TASMOTA 9.5.0",
    "measure_method": "script",
    "measure_description": "Used script in utils/measure from repository with SAMPLE_COUNT=10",
    "name": "Hue White and Color Ambiance A19 E26 1100lm",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ],
    "linked_lut": "signify/LCA006"
}

custom_components/powercalc/data/signify/LCA006/model.json METASEP
{
    "measure_device": "Nous A1 with TASMOTA 9.5.0",
    "measure_method": "script",
    "measure_description": "Used script in utils/measure from repository with SAMPLE_COUNT=10",
    "name": "Hue White and Color Ambiance 1100 E27 with BT (9290024688)",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LCA006/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCA006/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/LCA001/model.json METASEP
{
    "name": "Hue White and Color Ambiance A19 E26/E27 (Gen 5)",
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "standby_power": 0.33,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/LCA001/hs.csv.gz METASEP

custom_components/powercalc/data/signify/LCA001/color_temp.csv.gz METASEP

custom_components/powercalc/data/signify/1742930P7/model.json METASEP
{
    "measure_description": "Measured with utils/measure script using OCR",
    "measure_device": "Zhurui PR10",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "master"
    },
    "name": "Hue Impress Outdoor Wall Light Small",
    "standby_power": 0.17,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/signify/1742930P7/hs.csv.gz METASEP

custom_components/powercalc/data/signify/1742930P7/color_temp.csv.gz METASEP

custom_components/powercalc/data/paulmann licht/371000002/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.20.1:docker"
    },
    "name": "Amaris RGBW LED Panels 595x295mm",
    "standby_power": 1.33,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/paulmann licht/371000002/hs.csv.gz METASEP

custom_components/powercalc/data/paulmann licht/371000002/color_temp.csv.gz METASEP

custom_components/powercalc/data/osram/LIGHTIFY Indoor Flex RGBW/model.json METASEP
{
   "name":"LIGHTIFY Indoor Flex RGBW 3P (1.8m)",
   "standby_power":0.3,
   "supported_modes":[
      "lut"
   ],
   "measure_device":"Gosund SP111 on Tasmota 9.5.0",
   "measure_description":"Measure Script for Tasmota",
   "measure_method":"script"
}

custom_components/powercalc/data/osram/LIGHTIFY Indoor Flex RGBW/hs.csv.gz METASEP

custom_components/powercalc/data/osram/LIGHTIFY Indoor Flex RGBW/color_temp.csv.gz METASEP

custom_components/powercalc/data/osram/CLA60 TW/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, 1PM plus",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 3,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.11:docker"
    },
    "name": "Smart+ Classic E27 tunable white",
    "standby_power": 0.5,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/osram/CLA60 TW/color_temp.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/ZBT-ColorTemperature/model.json METASEP
{
    "name": "tint GU10 Spot 350lm",
    "standby_power": 0.31,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured once with script made by bramstroker using 9 sec pauses and partially verified manual"
}

custom_components/powercalc/data/mueller-licht/ZBT-ColorTemperature/color_temp.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/45328/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.20.3:docker"
    },
    "name": "M\u00fcller Licht E14 Kerze white+color",
    "standby_power": 0.31,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/mueller-licht/45328/hs.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/45328/color_temp.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/45318/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.20.3:docker"
    },
    "name": "M\u00fcller Licht GU10 Spot white+color",
    "standby_power": 0.15,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/mueller-licht/45318/hs.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/45318/color_temp.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/45317/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.20.3:docker"
    },
    "name": "M\u00fcller Licht E27 Bulb white+color",
    "standby_power": 0.43,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/mueller-licht/45317/hs.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/45317/color_temp.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/45311/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.20.3:docker"
    },
    "name": "M\u00fcller Licht E27 Globe Gold",
    "standby_power": 0.26,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/mueller-licht/45311/color_temp.csv.gz METASEP

custom_components/powercalc/data/mueller-licht/43961/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.20.3:docker"
    },
    "name": "M\u00fcller Licht GU10 Spot tint white",
    "standby_power": 0.33,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/mueller-licht/43961/color_temp.csv.gz METASEP

custom_components/powercalc/data/melitec/DP15/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "master"
    },
    "name": "LightZone DP15 LED-Pendelleuchte",
    "standby_power": 0.83,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/melitec/DP15/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Z/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Wemo Insight F7C029uk",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.17.0:docker"
    },
    "name": "LIFX Z",
    "standby_power": 0.57,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lifx/LIFX Original 1000/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa KP115",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 1,
        "VERSION": "v0.17.0:docker"
    },
    "name": "LIFX Original 1000",
    "standby_power": 2.46,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lifx/LIFX Original 1000/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Original 1000/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Mini Color/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "TP-Link KP125",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.12:docker"
    },
    "name": "LIFX Mini Color",
    "standby_power": 0.5,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/lifx/LIFX Mini Color/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Mini Color/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Candle/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa KP115",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 1,
        "VERSION": "v0.17.0:docker"
    },
    "name": "LIFX Candle",
    "standby_power": 0.56,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lifx/LIFX Candle/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX Candle/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX BR30 Night Vision/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa KP115",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 1,
        "VERSION": "v0.17.0:docker"
    },
    "name": "LIFX BR30 Night Vision",
    "standby_power": 0.72,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/lifx/LIFX A19/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Gosuna Smart Plug UP111 (via tuya)",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.15:docker"
    },
    "name": "LIFX A19",
    "standby_power": 0.50,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lifx/LIFX A19/hs.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX A19/color_temp.csv.gz METASEP

custom_components/powercalc/data/lifx/LIFX A19 Night Vision/model.json METASEP
{
    "measure_description": "Measured with utils/measure script. TP-link kp125 was having inconsistent readings towards the 0.52 watt and lower range, so I manually smoothed out some of the outliers",
    "measure_device": "TP-Link KP125",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.12:docker"
    },
    "name": "LIFX A19 Night Vision",
    "standby_power": 0.47,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/lidl/HG08010/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Gosund SP111 with Tasmota",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.22.0:docker"
    },
    "name": "Livarno Lux LED Outdoor Floodlight RGBW",
    "standby_power": 0.35,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lidl/HG08010/hs.csv.gz METASEP

custom_components/powercalc/data/lidl/HG08010/color_temp.csv.gz METASEP

custom_components/powercalc/data/lidl/HG07834C/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa HS110",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.5:docker"
    },
    "name": "Livarno Lux E27 bulb RGB",
    "standby_power": 0.42,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lidl/HG07834C/hs.csv.gz METASEP

custom_components/powercalc/data/lidl/HG07834C/color_temp.csv.gz METASEP

custom_components/powercalc/data/lidl/HG07834B/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 1,
        "VERSION": "master"
    },
    "name": "Livarno Lux E14 candle RGB",
    "standby_power": 0.38,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lidl/HG07834B/hs.csv.gz METASEP

custom_components/powercalc/data/lidl/HG07834B/color_temp.csv.gz METASEP

custom_components/powercalc/data/lidl/HG06492B/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa HS110",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.5:docker"
    },
    "name": "Livarno Lux E14 candle CCT",
    "standby_power": 0.31,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lidl/HG06492B/color_temp.csv.gz METASEP

custom_components/powercalc/data/lidl/HG06462A/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Gosund SP111 with Tasmota",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.22.0:docker"
    },
    "name": "Livarno Lux E27 filament bulb",
    "standby_power": 0.2,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lidl/HG06462A/brightness.csv.gz METASEP

custom_components/powercalc/data/lidl/HG06106C/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa HS110",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.5:docker"
    },
    "name": "Livarno Lux E27 bulb RGB",
    "standby_power": 0.36,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/lidl/HG06106C/hs.csv.gz METASEP

custom_components/powercalc/data/lidl/HG06106C/color_temp.csv.gz METASEP

custom_components/powercalc/data/lidl/HG06106C/brightness.csv.gz METASEP

custom_components/powercalc/data/lidl/HG06106B/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa HS110",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.19.5:docker"
    },
    "name": "Livarno Lux E14 candle RGB",
    "standby_power": 0.29,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lidl/HG06106B/hs.csv.gz METASEP

custom_components/powercalc/data/lidl/HG06106B/color_temp.csv.gz METASEP

custom_components/powercalc/data/lidl/HG06106B/brightness.csv.gz METASEP

custom_components/powercalc/data/lexman/IG-CDZOTAAG014RA-MAN/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "GreenWave - GWPN1",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.18.4:docker"
    },
    "name": "ENKI Lexman E27 14W to 100W LED RGBW v2",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/lexman/IG-CDZOTAAG014RA-MAN/hs.csv.gz METASEP

custom_components/powercalc/data/lexman/IG-CDZOTAAG014RA-MAN/color_temp.csv.gz METASEP

custom_components/powercalc/data/ledvance/Tibea TW Z3/model.json METASEP
{
    "name": "Tibea Lamp E27 Tuneable White 2000lm",
    "standby_power": 0.51,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured twice with script made by bramstroker and partially verified manual"
}

custom_components/powercalc/data/ledvance/Tibea TW Z3/color_temp.csv.gz METASEP

custom_components/powercalc/data/ledvance/LDVWF30FILE27RGBW/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 1,
        "VERSION": "master"
    },
    "name": "Ledvance LDVWF30FILE27RGBW",
    "standby_power": 1.04,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ledvance/LDVWF30FILE27RGBW/hs.csv.gz METASEP

custom_components/powercalc/data/ledvance/LDVWF30FILE27RGBW/color_temp.csv.gz METASEP

custom_components/powercalc/data/innr/RS 125/model.json METASEP
{
    "name": "RS125 GU10 350lm 2700K",
    "standby_power": 0.38,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
    "measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
}

custom_components/powercalc/data/innr/RS 125/brightness.csv.gz METASEP

custom_components/powercalc/data/innr/RB 285 C/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 4,
        "SLEEP_TIME": 2
    },
    "name": "RB 285 C 806lm E27 RGBW",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/innr/RB 285 C/hs.csv.gz METASEP

custom_components/powercalc/data/innr/RB 285 C/color_temp.csv.gz METASEP

custom_components/powercalc/data/innr/RB 250 C/model.json METASEP
{
    "measure_description": "Measured 3 bulbs simultaneously with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "master"
    },
    "name": "RB 250 C 470lm E14 RGBW",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/innr/RB 250 C/hs.csv.gz METASEP

custom_components/powercalc/data/innr/RB 250 C/color_temp.csv.gz METASEP

custom_components/powercalc/data/innr/RB 175 W/model.json METASEP
{
    "name": "RB175W E27 806lm 2200K-2700K automatic warm dimming",
    "standby_power": 0.35,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
    "measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
}

custom_components/powercalc/data/innr/RB 175 W/brightness.csv.gz METASEP

custom_components/powercalc/data/innr/BY 265/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Kasa, KP115",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.17.0:docker"
    },
    "name": "BY 265 806lm B22 2700k",
    "standby_power": 0.44,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/innr/BY 265/brightness.csv.gz METASEP

custom_components/powercalc/data/innr/AE 280 C/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Sonoff, S31",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 3,
        "SLEEP_TIME": 15,
        "VERSION": "v0.19.16:docker"
    },
    "name": "Smart Bulb Color E27",
    "standby_power": 0.11,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/innr/AE 280 C/hs.csv.gz METASEP

custom_components/powercalc/data/innr/AE 280 C/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/T1820/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.13.4"
    },
    "name": "LEPTITER Recessed spot light",
    "standby_power": 0.3,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/T1820/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/Slagsida/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "master"
    },
    "name": "Ikea Slagsida",
    "standby_power": 0.62,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/Slagsida/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED2005R5/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 3,
        "SLEEP_TIME": 6,
        "VERSION": "master"
    },
    "name": "TRADFRI LED bulb GU10 345 lumen, dimmable, white spectrum",
    "standby_power": 0.2,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED2005R5/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED2003G10/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 3,
        "SLEEP_TIME": 6,
        "VERSION": "master"
    },
    "name": "TRADFRI LED bulb E27 1055 lumen, dimmable, white spectrum, opal white",
    "standby_power": 0.17,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED2003G10/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED2002G5/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.13.4"
    },
    "name": "TRADFRIbulbE14WSglobeopal470lm",
    "standby_power": 0.23,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED2002G5/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1949C5/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.18.4:docker"
    },
    "name": "TRADFRI bulb E14 WS candle opal 470lm",
    "standby_power": 0.2,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED1949C5/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1935C3/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.22.1:docker"
    },
    "name": "TRADFRIbulbE14WWclear250lm",
    "standby_power": 0.1,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED1935C3/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1934G3/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 3,
        "VERSION": "v0.22.1:docker"
    },
    "name": "TRADFRIbulbE27WWclear250lm",
    "standby_power": 0.27,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED1934G3/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1925G6/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.18.4:docker"
    },
    "name": "TRADFRI bulb E14 CWS 470lm",
    "standby_power": 0.23,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED1925G6/hs.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1925G6/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1924G9/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.14.1"
    },
    "name": "TRADFRI bulb E27 CWS 806lm",
    "standby_power": 0.2,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED1924G9/hs.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1924G9/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1923R5/model.json METASEP
{
    "measure_description": "Measured with utils/measure script, manual correction of values measured as 0.0",
    "measure_device": "Shelly, 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.14.1"
    },
    "name": "TRADFRI bulb GU10 CWS 345lm",
    "standby_power": 0.18,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED1923R5/hs.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1923R5/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1842G3/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "sonoff S31",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 5,
        "VERSION": "v0.13.5"
    },
    "name": "TRADFRI bulb E27 WW clear 250lm",
    "standby_power": 0.551,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED1842G3/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1837R5/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.13.4"
    },
    "name": "TRADFRI bulb GU10 WW 400lm",
    "standby_power": 0.21,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED1837R5/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1836G9/model.json METASEP
{
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "name": "TRADFRI bulb E27 WW 806lm",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED1836G9/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1835C6/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.13.4"
    },
    "name": "TRADFRI bulb E14 WS 470lm",
    "standby_power": 0.25,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED1835C6/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1738G7/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 5,
        "VERSION": "v0.13.1"
    },
    "name": "TRADFRI bulb E14 WS opal 600lm",
    "standby_power": 0.17,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED1738G7/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1736G9/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.13.4"
    },
    "name": "TRADFRI bulb E27 WS clear 806lm",
    "standby_power": 0.34,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED1736G9/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1733G7/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.13.4"
    },
    "name": "TRADFRI bulb E14 WS opal 600lm",
    "standby_power": 0.33,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/LED1733G7/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1732G11/model.json METASEP
{
    "name": "TRADFRI bulb E27 WS opal 1000lm",
    "standby_power": 0.26,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured with script made by bramstroker and partially verified manual"
}

custom_components/powercalc/data/ikea/LED1732G11/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1650R5/model.json METASEP
{
    "name": "TRADFRI bulb GU10 W 400lm",
    "standby_power": 0.38,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
    "measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
}

custom_components/powercalc/data/ikea/LED1650R5/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1649C5/model.json METASEP
{
    "name": "TRADFRI bulb E14 W op/ch 400lm",
    "standby_power": 0.40,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
    "measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
}

custom_components/powercalc/data/ikea/LED1649C5/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1624G9/model.json METASEP
{
    "measure_description": "Measured with utils/measure script, manually added values approximately corresponding with xy_color: [0.458, 0.41]",
    "measure_device": "Shelly 1PM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 2,
        "VERSION": "v0.13.4"
    },
    "name": "TRADFRI bulb E27 CWS opal 600lm",
    "standby_power": 0.48,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED1624G9/hs.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1624G9/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1623G12/model.json METASEP
{
    "name": "TRADFRI bulb E27 opal 1000lm",
    "standby_power": 0.36,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
    "measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
}

custom_components/powercalc/data/ikea/LED1623G12/brightness.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1546G12/model.json METASEP
{
    "name": "TRADFRI bulb E27 WS clear 950lm",
    "standby_power": 0.43,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured with script made by bramstroker and partially verified manual"
}

custom_components/powercalc/data/ikea/LED1546G12/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1545G12/model.json METASEP
{
    "measure_device": "unknown",
    "measure_method": "script",
    "name": "TRADFRI bulb E27 WS opal 980lm",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/ikea/LED1545G12/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1537R6/model.json METASEP
{
    "name": "TRADFRI bulb GU10 WS 400lm",
    "standby_power": 0.44,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured once with old version script made by bramstroker using 6 sec pauses and partially verified and fixed manual"
}

custom_components/powercalc/data/ikea/LED1537R6/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/LED1536G5/model.json METASEP
{
    "name": "TRADFRI bulb E14 WS opal 400lm",
    "standby_power": 0.44,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S",
    "measure_description": "Measured with script made by bramstroker and partially verified manual"
}

custom_components/powercalc/data/ikea/LED1536G5/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/L1529/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "SHPLG-S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 50,
        "SLEEP_TIME": 10,
        "VERSION": "master"
    },
    "name": "FLOALT panel WS 60x60",
    "standby_power": 0.12,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/L1529/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/L1528/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly 3EM",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 1,
        "SLEEP_TIME": 3,
        "VERSION": "v0.17.0:docker"
    },
    "name": "FLOALT panel WS 30x90",
    "standby_power": 0.45,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/L1528/color_temp.csv.gz METASEP

custom_components/powercalc/data/ikea/L1527/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly, SHPLG-S",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 25,
        "SLEEP_TIME": 10,
        "VERSION": "master"
    },
    "name": "FLOALT panel WS 30x30",
    "standby_power": 0.08,
    "supported_modes": [
        "lut"
    ]
}
custom_components/powercalc/data/ikea/L1527/color_temp.csv.gz METASEP

custom_components/powercalc/data/belkin/F7C033/model.json METASEP
{
    "name": "WeMo smart LED bulb",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "manual",
    "measure_device": "RRPM02 (Model); 4897037690801 (Barcode); Reduction Revolution Plug-in Power Meter",
    "measure_description": "Was not able to measure the standby power, the value is assumed"
}

custom_components/powercalc/data/belkin/F7C033/brightness.csv.gz METASEP

custom_components/powercalc/data/aqara/ZNLDP12LM/model.json METASEP
{
    "measure_description": "Measured with utils/measure script",
    "measure_device": "Shelly Plug E",
    "measure_method": "script",
    "measure_settings": {
        "SAMPLE_COUNT": 2,
        "SLEEP_TIME": 12,
        "VERSION": "v0.10.4"
    },
    "name": "Aqara LED light bulb (tunable white)",
    "standby_power": 0.41,
    "supported_modes": [
        "lut"
    ]
}

custom_components/powercalc/data/aqara/ZNLDP12LM/color_temp.csv.gz METASEP

docs/yeelight.md METASEP
# Adding Yeelights to Powercalc
Presently, Home Assistant (and the underlying python-yeelight library) does not autodetect the specific model of some Yeelight devices, when adding them via the UI. They may detect the general type (such as *'color'*), but not the generation (like *'color2'*, or actual model number). This prevents us from configuring these devices automatically. To work around this, you must configure those lights manually in some way.

If you know how to find your Yeelights' IP addresses, you can do this with Home Assistant directly:

1. Remove the affected lights from the UI (on the Integrations page)
2. Re-add them to your YAML configuration, according to the [format shown here](https://www.home-assistant.io/integrations/yeelight/#full-configuration), making sure to add the `model` option
3. Restart Home Assistant and confirm working
4. (Optional) Remove them from your YAML configuration. If working, they should now persist in Home Assistant after restarts, with correct model retained

You can alternatively configure each of them manually with Powercalc, specifying `manufacturer` and `model` options.
docs/supported_models.md METASEP
# Supported models (143 total)
| manufacturer |        model id         |                                name                                 |calculation modes|      color modes       |                                            aliases                                            |
|--------------|-------------------------|---------------------------------------------------------------------|-----------------|------------------------|-----------------------------------------------------------------------------------------------|
|aqara         |ZNLDP12LM                |Aqara LED light bulb (tunable white)                                 |lut              |color_temp              |                                                                                               |
|belkin        |F7C033                   |WeMo smart LED bulb                                                  |lut              |brightness              |                                                                                               |
|ikea          |L1527                    |FLOALT panel WS 30x30                                                |lut              |color_temp              |FLOALT panel WS 30x30                                                                          |
|ikea          |L1528                    |FLOALT panel WS 30x90                                                |lut              |color_temp              |                                                                                               |
|ikea          |L1529                    |FLOALT panel WS 60x60                                                |lut              |color_temp              |FLOALT panel WS 60x60                                                                          |
|ikea          |LED1536G5                |TRADFRI bulb E14 WS opal 400lm                                       |lut              |color_temp              |TRADFRI bulb E14 WS opal 400lm                                                                 |
|ikea          |LED1537R6                |TRADFRI bulb GU10 WS 400lm                                           |lut              |color_temp              |TRADFRI bulb GU10 WS 400lm                                                                     |
|ikea          |LED1545G12               |TRADFRI bulb E27 WS opal 980lm                                       |lut              |color_temp              |TRADFRI bulb E27 WS opal 980lm                                                                 |
|ikea          |LED1546G12               |TRADFRI bulb E27 WS clear 950lm                                      |lut              |color_temp              |TRADFRI bulb E27 WS clear 950lm                                                                |
|ikea          |LED1623G12               |TRADFRI bulb E27 opal 1000lm                                         |lut              |brightness              |TRADFRI bulb E27 opal 1000lm,TRADFRI bulb E27 W opal 1000lm                                    |
|ikea          |LED1624G9                |TRADFRI bulb E27 CWS opal 600lm                                      |lut              |color_temp,hs           |TRADFRI bulb E14 CWS opal 600lm,TRADFRI bulb E26 CWS opal 600lm,TRADFRI bulb E27 CWS opal 600lm|
|ikea          |LED1649C5                |TRADFRI bulb E14 W op/ch 400lm                                       |lut              |brightness              |TRADFRI bulb E14 W op/ch 400lm                                                                 |
|ikea          |LED1650R5                |TRADFRI bulb GU10 W 400lm                                            |lut              |brightness              |TRADFRI bulb GU10 W 400lm                                                                      |
|ikea          |LED1732G11               |TRADFRI bulb E27 WS opal 1000lm                                      |lut              |color_temp              |TRADFRI bulb E27 WS opal 1000lm                                                                |
|ikea          |LED1733G7                |TRADFRI bulb E14 WS opal 600lm                                       |lut              |color_temp              |                                                                                               |
|ikea          |LED1736G9                |TRADFRI bulb E27 WS clear 806lm                                      |lut              |color_temp              |TRADFRI bulb E27 WS clear 806lm                                                                |
|ikea          |LED1738G7                |TRADFRI bulb E14 WS opal 600lm                                       |lut              |color_temp              |TRADFRI bulb E14 WS opal 600lm                                                                 |
|ikea          |LED1835C6                |TRADFRI bulb E14 WS 470lm                                            |lut              |color_temp              |TRADFRI bulb E14 WS 470lm                                                                      |
|ikea          |LED1836G9                |TRADFRI bulb E27 WW 806lm                                            |lut              |brightness              |TRADFRI bulb E27 WW 806lm                                                                      |
|ikea          |LED1837R5                |TRADFRI bulb GU10 WW 400lm                                           |lut              |brightness              |TRADFRI bulb GU10 WW 400lm                                                                     |
|ikea          |LED1842G3                |TRADFRI bulb E27 WW clear 250lm                                      |lut              |brightness              |TRADFRI bulb E27 WW clear 250lm                                                                |
|ikea          |LED1923R5                |TRADFRI bulb GU10 CWS 345lm                                          |lut              |color_temp,hs           |TRADFRI bulb GU10 CWS 345lm                                                                    |
|ikea          |LED1924G9                |TRADFRI bulb E27 CWS 806lm                                           |lut              |color_temp,hs           |TRADFRI bulb E27 CWS 806lm                                                                     |
|ikea          |LED1925G6                |TRADFRI bulb E14 CWS 470lm                                           |lut              |color_temp,hs           |TRADFRI bulb E14 CWS 470lm                                                                     |
|ikea          |LED1934G3                |TRADFRIbulbE27WWclear250lm                                           |lut              |brightness              |                                                                                               |
|ikea          |LED1935C3                |TRADFRIbulbE14WWclear250lm                                           |lut              |brightness              |                                                                                               |
|ikea          |LED1949C5                |TRADFRI bulb E14 WS candle opal 470lm                                |lut              |color_temp              |TRADFRIbulbE14WScandleopal470lm                                                                |
|ikea          |LED2002G5                |TRADFRIbulbE14WSglobeopal470lm                                       |lut              |color_temp              |TRADFRIbulbE14WSglobeopal470lm                                                                 |
|ikea          |LED2003G10               |TRADFRI LED bulb E27 1055 lumen, dimmable, white spectrum, opal white|lut              |color_temp              |TRADFRIbulbE27WSglobeopal1055lm                                                                |
|ikea          |LED2005R5                |TRADFRI LED bulb GU10 345 lumen, dimmable, white spectrum            |lut              |color_temp              |TRADFRIbulbGU10WS345lm,TRADFRI bulb GU10 WW 345lm                                              |
|ikea          |Slagsida                 |Ikea Slagsida                                                        |lut              |brightness              |                                                                                               |
|ikea          |T1820                    |LEPTITER Recessed spot light                                         |lut              |color_temp              |LEPTITER Recessed spot light                                                                   |
|innr          |AE 280 C                 |Smart Bulb Color E27                                                 |lut              |color_temp,hs           |                                                                                               |
|innr          |BY 265                   |BY 265 806lm B22 2700k                                               |lut              |brightness              |                                                                                               |
|innr          |RB 175 W                 |RB175W E27 806lm 2200K-2700K automatic warm dimming                  |lut              |brightness              |                                                                                               |
|innr          |RB 250 C                 |RB 250 C 470lm E14 RGBW                                              |lut              |color_temp,hs           |                                                                                               |
|innr          |RB 285 C                 |RB 285 C 806lm E27 RGBW                                              |lut              |color_temp,hs           |                                                                                               |
|innr          |RS 125                   |RS125 GU10 350lm 2700K                                               |lut              |brightness              |                                                                                               |
|ledvance      |LDVWF30FILE27RGBW        |Ledvance LDVWF30FILE27RGBW                                           |lut              |color_temp,hs           |                                                                                               |
|ledvance      |Tibea TW Z3              |Tibea Lamp E27 Tuneable White 2000lm                                 |lut              |color_temp              |                                                                                               |
|lexman        |IG-CDZOTAAG014RA-MAN     |ENKI Lexman E27 14W to 100W LED RGBW v2                              |lut              |color_temp,hs           |                                                                                               |
|lidl          |HG06106B                 |Livarno Lux E14 candle RGB                                           |lut              |color_temp,brightness,hs|                                                                                               |
|lidl          |HG06106C                 |Livarno Lux E27 bulb RGB                                             |lut              |color_temp,brightness,hs|                                                                                               |
|lidl          |HG06462A                 |Livarno Lux E27 filament bulb                                        |lut              |brightness              |                                                                                               |
|lidl          |HG06492B                 |Livarno Lux E14 candle CCT                                           |lut              |color_temp              |                                                                                               |
|lidl          |HG07834B                 |Livarno Lux E14 candle RGB                                           |lut              |color_temp,hs           |                                                                                               |
|lidl          |HG07834C                 |Livarno Lux E27 bulb RGB                                             |lut              |color_temp,hs           |                                                                                               |
|lidl          |HG08010                  |Livarno Lux LED Outdoor Floodlight RGBW                              |lut              |color_temp,hs           |                                                                                               |
|lifx          |LIFX A19                 |LIFX A19                                                             |lut              |color_temp,hs           |                                                                                               |
|lifx          |LIFX A19 Night Vision    |LIFX A19 Night Vision                                                |lut              |hs                      |                                                                                               |
|lifx          |LIFX BR30 Night Vision   |LIFX BR30 Night Vision                                               |lut              |color_temp,hs           |                                                                                               |
|lifx          |LIFX Candle              |LIFX Candle                                                          |lut              |color_temp,hs           |                                                                                               |
|lifx          |LIFX Mini Color          |LIFX Mini Color                                                      |lut              |color_temp,hs           |                                                                                               |
|lifx          |LIFX Original 1000       |LIFX Original 1000                                                   |lut              |color_temp,hs           |                                                                                               |
|lifx          |LIFX Z                   |LIFX Z                                                               |lut              |color_temp,hs           |                                                                                               |
|melitec       |DP15                     |LightZone DP15 LED-Pendelleuchte                                     |lut              |color_temp              |                                                                                               |
|mueller-licht |43961                    |Müller Licht GU10 Spot tint white                                    |lut              |color_temp              |                                                                                               |
|mueller-licht |45311                    |Müller Licht E27 Globe Gold                                          |lut              |color_temp              |                                                                                               |
|mueller-licht |45317                    |Müller Licht E27 Bulb white+color                                    |lut              |color_temp,hs           |                                                                                               |
|mueller-licht |45318                    |Müller Licht GU10 Spot white+color                                   |lut              |color_temp,hs           |                                                                                          45327|
|mueller-licht |45328                    |Müller Licht E14 Kerze white+color                                   |lut              |color_temp,hs           |                                                                                               |
|mueller-licht |ZBT-ColorTemperature     |tint GU10 Spot 350lm                                                 |lut              |color_temp              |                                                                                               |
|osram         |CLA60 TW                 |Smart+ Classic E27 tunable white                                     |lut              |color_temp              |                                                                                               |
|osram         |LIGHTIFY Indoor Flex RGBW|LIGHTIFY Indoor Flex RGBW 3P (1.8m)                                  |lut              |color_temp,hs           |                                                                                               |
|paulmann licht|371000002                |Amaris RGBW LED Panels 595x295mm                                     |lut              |color_temp,hs           |                                                                                               |
|signify       |1742930P7                |Hue Impress Outdoor Wall Light Small                                 |lut              |color_temp,hs           |                                                                                               |
|signify       |LCA001                   |Hue White and Color Ambiance A19 E26/E27 (Gen 5)                     |lut              |color_temp,hs           |9290022166,929003053401,LCA005,9290022266A                                                     |
|signify       |LCA006                   |Hue White and Color Ambiance 1100 E27 with BT (9290024688)           |lut              |color_temp,hs           |                                                                                               |
|signify       |LCA007                   |Hue White and Color Ambiance A19 E26 1100lm                          |lut              |color_temp,hs           |                                                                                     9290024687|
|signify       |LCA008                   |Hue White and Color Ambiance E27 1600lm                              |lut              |color_temp,hs           |                                                                                   929002471601|
|signify       |LCA009                   |Hue color lamp (LCA009)                                              |lut              |color_temp,hs           |                                                                                               |
|signify       |LCE002                   |Hue White and Color Ambiance E14 Candle w/ BT                        |lut              |color_temp,hs           |                                                                                               |
|signify       |LCF002                   |Hue Calla Outdoor Pedestal                                           |lut              |color_temp,hs           |                                                                                               |
|signify       |LCG002                   |Hue White and Color Ambiance GU10 BT                                 |lut              |color_temp,hs           |                                                                                   929001953101|
|signify       |LCL001                   |Hue white and color ambiance LightStrip plus                         |lut              |color_temp,hs           |                                                                                  8718699703424|
|signify       |LCS001                   |Hue Lily Outdoor Spot Light RGBCCT                                   |lut              |color_temp,hs           |1741430P7,1741530P7                                                                            |
|signify       |LCT001                   |Philips Hue White and Color Ambience (1 gen)                         |lut              |color_temp,hs           |                                                                                               |
|signify       |LCT003                   |Hue White and Color Ambiance Spot GU10                               |lut              |color_temp,hs           |                                                                                               |
|signify       |LCT007                   |Hue White and Color E27 Gen2 (2015)                                  |lut              |color_temp,hs           |                                                                                               |
|signify       |LCT010                   |Hue White and Color Ambiance A19 E26 (Gen 3)                         |lut              |color_temp,hs           |                                                                                               |
|signify       |LCT012                   |Hue White and Color Ambiance Candle E12                              |lut              |color_temp,hs           |                                                                                               |
|signify       |LCT014                   |Philips Hue 800 Lumen White and Color A19                            |lut              |color_temp,hs           |                                                                                               |
|signify       |LCT015                   |Hue White and Color Ambiance A19 E26/E27 (Gen 4)                     |lut              |color_temp,hs           |9290012573A                                                                                    |
|signify       |LCT016                   |Philips Hue 800 Lumen White and Color A19                            |lut              |color_temp,hs           |                                                                                               |
|signify       |LCT024                   |Hue Play                                                             |lut              |color_temp,hs           |                                                                                   440400982841|
|signify       |LCT026                   |Hue Go 2.0                                                           |lut              |color_temp,hs           |7602031P7                                                                                      |
|signify       |LLC001                   |Philips Living Color Iris                                            |lut              |hs                      |                                                                                               |
|signify       |LLC006                   |LivingColors Iris Table Lamp Gen3                                    |lut              |brightness,hs           |                                                                                               |
|signify       |LLC010                   |Hue Iris                                                             |lut              |hs                      |                                                                                               |
|signify       |LLC011                   |Living Colours Bloom 7099760PH                                       |lut              |hs                      |                                                                                               |
|signify       |LLC012                   |Hue Bloom Table Lamp                                                 |lut              |hs                      |                                                                                               |
|signify       |LLC020                   |Hue Go                                                               |lut              |color_temp,hs           |                                                                                               |
|signify       |LST001                   |Hue LightStrip                                                       |lut              |hs                      |7299355PH                                                                                      |
|signify       |LST002                   |Hue LightStrip Plus (2m)                                             |lut              |color_temp,hs           |                                                                                   915005106701|
|signify       |LST004                   |Hue White and Color Ambiance LED Outdoor Lightstrip (5m)             |lut              |color_temp,hs           |                                                                                               |
|signify       |LTA001                   |Hue White Ambiance E27                                               |lut              |color_temp              |                                                                                     9290022169|
|signify       |LTA003                   |Hue White Ambiance E26                                               |lut              |color_temp              |                                                                                               |
|signify       |LTA004                   |Philips Hue White Ambiance 800 E27 Bluetooth                         |lut              |color_temp              |                                                                                  8719514328242|
|signify       |LTA009                   |Hue White Ambiance 1100 E27 Bluetooth                                |lut              |color_temp              |                                                                                               |
|signify       |LTA011                   |Hue White Ambiance 1600 E27 w/ BT                                    |lut              |color_temp              |                                                                                     9290024719|
|signify       |LTC001                   |Hue Being Ceiling Light                                              |lut              |color_temp              |3261030P6,3261031P6,3261048P6                                                                  |
|signify       |LTC002                   |Hue Fair Ceiling Lamp                                                |lut              |color_temp              |                                                                                               |
|signify       |LTC012                   |Hue Struana Ceiling Light                                            |lut              |color_temp              |3418931P6                                                                                      |
|signify       |LTE002                   |Hue White Ambiance E14 w/ BT                                         |lut              |color_temp              |                                                                                               |
|signify       |LTG002                   |Hue White Ambiance GU10 w/ BT                                        |lut              |color_temp              |                                                                                               |
|signify       |LTO001                   |Hue G93 E27 Filament Globe Bulb                                      |lut              |color_temp              |                                                                                               |
|signify       |LTP003                   |Hue Fair Pendant                                                     |lut              |color_temp              |                                                                                               |
|signify       |LTP008                   |Hue Being Pendant Light                                              |lut              |color_temp              |                                                                                               |
|signify       |LTW001                   |Hue White Ambiance A19                                               |lut              |color_temp              |                                                                                               |
|signify       |LTW010                   |Hue White Ambiance A60 E27 806lm 9290012001A                         |lut              |color_temp              |                                                                                               |
|signify       |LTW012                   | Hue White Ambiance Candle E14                                       |lut              |color_temp              |                                                                                               |
|signify       |LTW013                   |Hue white ambiance GU10                                              |lut              |color_temp              |                                                                                               |
|signify       |LTW017                   |Hue Adore Bathroom Mirror Light                                      |lut              |color_temp              |3417711P6                                                                                      |
|signify       |LWA001                   |Hue White Ambiance Bulb A60 E27                                      |lut              |brightness              |                                                                                  8718699673147|
|signify       |LWA003                   |Hue White A19 E26 Bluetooth                                          |lut              |brightness              |                                                                                     9290022268|
|signify       |LWA004                   |Hue White Filament Bulb A60 E27                                      |lut              |brightness              |                                                                                               |
|signify       |LWA005                   |Hue Filament E26                                                     |lut              |brightness              |                                                                                               |
|signify       |LWA008                   |Hue White Bulb A21 E26 1600lm with Bluetooth                         |lut              |brightness              |                                                                                     9290023351|
|signify       |LWA009                   |Hue White 1600 A67 E27 1600lm                                        |lut              |brightness              |                                                                                               |
|signify       |LWA017                   |Hue white A60 bulb E27 1050lm with Bluetooth                         |lut              |brightness              |                                                                                               |
|signify       |LWB004                   |Hue Lux E27 750lm                                                    |lut              |brightness              |                                                                                         433714|
|signify       |LWB006                   |Hue White A60 E27 800lm                                              |lut              |brightness              |                                                                                               |
|signify       |LWB010                   |Hue White A60 E27 806lm                                              |lut              |brightness              |                                                                                  8718696449691|
|signify       |LWB014                   |Philips Hue 840 Lumen White A19                                      |lut              |brightness              |                                                                                               |
|signify       |LWE002                   |Hue White E14 SES Candle 470lm w/ BT (LWE002)                        |lut              |brightness              |                                                                        87186996712119290020399|
|signify       |LWF001                   |Hue white A60 bulb E27                                               |lut              |brightness              |9290011370B                                                                                    |
|signify       |LWG001                   |Hue White GU10                                                       |lut              |brightness              |                                                                                               |
|signify       |LWG004                   |Hue White GU10 Bluetooth                                             |lut              |brightness              |                                                                                               |
|signify       |LWO001                   |Hue White Filament Bulb G93 E27 w/ BT                                |lut              |brightness              |                                                                                               |
|signify       |LWO002                   |Hue White G25 E26 Edison Filament Globe Bluetooth                    |lut              |brightness              |                                                                                     9290022415|
|signify       |LWO003                   |Hue White Filament Bulb G125 E27 w/ BT                               |lut              |brightness              |                                                                                               |
|signify       |LWU001                   |Hue P45 light bulb                                                   |lut              |brightness              |                                                                                     9290024406|
|signify       |LWV001                   |Hue White Filament Bulb ST64 E27                                     |lut              |brightness              |                                                                                               |
|signify       |LWV002                   |Hue Single Filament Bulb ST19 E26                                    |lut              |brightness              |                                                                                    46677551780|
|signify       |LWW001                   |Hue white A60 bulb E27                                               |lut              |brightness              |                                                                                               |
|sonoff        |B02BA60                  |Sonoff B02 Light with dimmer and temperature                         |lut              |color_temp              |                                                                                               |
|tuya          |NO66-ZB                  |Tuya Zigbee LED Smart Strip DC12V RGBCCT 60led/m                     |lut              |color_temp,hs           |                                                                                               |
|wiz           |9290023839               |WiZ Globe G95 E27 Color (type SHRGB1C, model 9290023839)             |lut              |color_temp,hs           |                                                                                               |
|yeelight      |YLDD05YL                 |Yeelight LED Light Strip 1S                                          |lut              |color_temp,hs           |strip6                                                                                         |
|yeelight      |YLDL01YL                 |Yeelight YLDL01YL Downlight                                          |lut              |color_temp,hs           |ceiling10                                                                                      |
|yeelight      |YLDP01YL                 |Yeelight LED 600lm 4000K 8W WiFi                                     |lut              |brightness              |mono1                                                                                          |
|yeelight      |YLDP06YL                 |Yeelight Smart LED Bulb II Color                                     |lut              |color_temp,hs           |color2                                                                                         |
|yeelight      |YLDP13YL                 |Yeelight LED Bulb 1S                                                 |lut              |color_temp,hs           |                                                                                               |

docs/.gitkeep METASEP

assets/preview.gif METASEP

pyproject.toml METASEP
[tool.isort]
profile = "black"
multi_line_output = 3

hacs.json METASEP
{
  "name": "Powercalc",
  "render_readme": false,
  "zip_release": true,
  "filename": "powercalc.zip",
  "homeassistant": "2021.11.0"
}

README.md METASEP
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs)
![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fpowercalc)
![Version](https://img.shields.io/github/v/release/bramstroker/homeassistant-powercalc)
![Downloads](https://img.shields.io/github/downloads/bramstroker/homeassistant-powercalc/total)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)

# :zap: PowerCalc: Home Assistant Virtual Power Sensors
Custom component to calculate estimated power consumption of lights and other appliances.
Provides easy configuration to get virtual power consumption sensors in Home Assistant for all your devices which don't have a build in power meter.
This component estimates power usage by looking at brightness, hue/saturation and color temperature etc using different strategies. They are explained below.

![Preview](https://raw.githubusercontent.com/bramstroker/homeassistant-powercalc/master/assets/preview.gif)

[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/bramski)

## TOC
- [Installation](#installation)
    - [HACS](#hacs)
    - [Manual](#manual)
- [Setup power sensors](#setup-power-sensors)
- [Configuration](#configuration)
    - [Sensor](#sensor-configuration)
    - [Global](#global-configuration)
- [Calculation modes](#calculation-modes)
    - [LUT](#lut-mode)
    - [Linear](#linear-mode)
    - [Fixed](#fixed-mode)
    - [WLED](#wled-mode)
- [Daily fixed energy](#daily-fixed-energy)
- [Sensor naming](#sensor-naming)
- [Setting up for energy dashboard](#setting-up-for-energy-dashboard)
- [Advanced features](#advanced-features)
    - [Multiple entities and grouping](#multiple-entities-and-grouping)
    - [Multiply Factor](#multiply-factor)
    - [Utility Meters](#utility-meters)
    - [Use real power sensor](#use-real-power-sensor)
    - [Resetting energy sensor](#resetting-energy-sensor)
- [Light model library](#light-model-library)
    - [LUT data files](#lut-data-files)
      - [Measuring lights](#creating-lut-files)
    - [Supported models](#supported-models)
- [Debug logging](#debug-logging)

## Installation

### HACS
This integration is part of the default HACS repository. Just click "Explore and add repository" to install

### Manual
Copy `custom_components/powercalc` into your Home Assistant `config` directory.

### Post installation steps
- Restart HA
- Add the following entry to `configuration.yaml`:
```yaml
powercalc:
```
- Restart HA final time

### Setup power sensors

Powercalc has a build-in library of more than 100 light models ([LUT](#lut-mode)), which have been measured and provided by users. See [supported models](docs/supported_models.md).

Starting from 0.12.0 Powercalc can automatically discover entities in your HA instance which are supported for automatic configuration.
After intallation and restarting HA power and energy sensors should appear. When this is not the case please check the logs for any errors.

When your appliance is not supported you have extensive options for manual configuration. These are explained below.

> Note: Manually configuring an entity will override an auto discovered entity

## Configuration

To manually add virtual sensors for your devices you have to add some configuration to `configuration.yaml`.
Additionally some settings can be applied on global level and will apply to all your virtual power sensors.
After changing the configuration you need to restart HA to get your power sensors to appear.

### Sensor configuration

For each entity you want to create a virtual power sensor for you'll need to add an entry in `configuration.yaml`.
Each virtual power sensor have it's own configuration possibilities.
They are as follows:

| Name                      | Type    | Requirement  | Description                                                                |
| ------------------------- | ------- | ------------ | -------------------------------------------------------------------------- |
| entity_id                 | string  | **Required** | HA entity ID. The id of the device you want your power sensor for          |
| unique_id                 | string  | **Optional** | Powercalc tries to get a unique_id from the source entity by default. When the source entity does not have a unique_id you can configure one yourself. This allows you to change the name, area etc from the GUI. You can use for example https://www.uuidgenerator.net/ to generate a unique one |
| manufacturer              | string  | **Optional** | Manufacturer, most of the time this can be automatically discovered        |
| model                     | string  | **Optional** | Model id, most of the time this can be automatically discovered            |
| standby_power             | float   | **Optional** | Supply the wattage when the device is off                                  |
| disable_standby_power     | boolean | **Optional** | Set to `true` to not show any power consumption when the device is standby |
| name                      | string  | **Optional** | Override the name                                                          |
| create_energy_sensor      | boolean | **Optional** | Set to disable/enable energy sensor creation. When set this will override global setting `create_energy_sensors` |
| create_utility_meters     | boolean | **Optional** | Set to disable/enable utility meter creation. When set this will override global setting `create_utility_meters` |
| utility_meter_types       | list    | **Optional** | Define which cycles you want to create utility meters for. See [cycle](https://www.home-assistant.io/integrations/utility_meter/#cycle). This will override global setting `utility_meter_types` |
| utility_meter_offset      | string  | **Optional** | Define the offset for utility meters. See [offset](https://www.home-assistant.io/integrations/utility_meter/#offset). |
| utility_meter_tariffs     | list    | **Optional** | Define different tariffs. See [tariffs](https://www.home-assistant.io/integrations/utility_meter/#tariffs). |
| custom_model_directory    | string  | **Optional** | Directory for a custom light model. Relative from the `config` directory   |
| power_sensor_category     | string  | **Optional** | Category for the created power sensor. See [generic-properties](https://developers.home-assistant.io/docs/core/entity/#generic-properties). |
| power_sensor_naming       | string  | **Optional** | Change the name (and id) of the sensors. Use the `{}` placeholder for the entity name of your appliance. When set this will override global setting `power_sensor_naming` |
| energy_sensor_category     | string  | **Optional** | Category for the created energy sensor. See [generic-properties](https://developers.home-assistant.io/docs/core/entity/#generic-properties). |
| energy_sensor_naming      | string  | **Optional** | Change the name (and id) of the sensors. Use the `{}` placeholder for the entity name of your appliance. When set this will override global setting `energy_sensor_naming` |
| energy_integration_method | string  | **Optional** | Integration method for the energy sensor. See [HA docs](https://www.home-assistant.io/integrations/integration/#method) |
| mode                      | string  | **Optional** | Calculation mode, one of `lut`, `linear`, `fixed`. The default mode is `lut` |
| multiply_factor           | float   | **Optional** | Multiplies the calculated power by this number. See [multiply factor](#multiply-factor) |
| multiply_factor_standby   | boolean | **Optional** | When set to `true` the `multiply_factor` will also be applied to the standby power |
| fixed                     | object  | **Optional** | [Fixed mode options](#fixed-mode)                                          |
| linear                    | object  | **Optional** | [Linear mode options](#linear-mode)                                        |
| wled                      | object  | **Optional** | [WLED mode options](#wled-mode)                                            |
| entities                  | list    | **Optional** | Makes it possible to add multiple entities at once in one powercalc entry. Also enable possibility to create group sensors automatically. See [multiple entities and grouping](#multiple-entities-and-grouping)  |
| create_group              | string  | **Optional** | This setting is only applicable when you also use `entities` setting or `include`. Define a group name here. See [multiple entities and grouping](#multiple-entities-and-grouping) |
| include                   | object  | **Optional** | Use this in combination with `create_group` to automatically include entities from a certain area, group or template. See [Include entities](#dynamically-including-entities)
| power_sensor_id           | string  | **Optional** | Entity id of an existing power sensor. This can be used to let powercalc create energy sensors and utility meters. This will create no virtual power sensor.
| energy_sensor_id           | string  | **Optional** | Entity id of an existing energy sensor. Mostly used in conjunction with `power_sensor_id`.
| ignore_unavailable_state  | boolean | **Optional** | Set this to `true` when you want the power sensor to display a value (0 or `standby_power`) regardless of whether the source entity is available. The can be useful for example on a TV which state can become unavailable when it is set to off. |
| calculation_enabled_condition | template | **Optional** | The configured power calculation strategy will only be executed when this template results in True, otherwise the power sensor will display 0  |

**Minimalistic example creating two power sensors:**

```yaml
sensor:
  - platform: powercalc
    entity_id: light.hallway
  - platform: powercalc
    entity_id: light.living_room
```

This will add a power sensors with the entity ids `sensor.hallway_power` and `sensor.living_room_power` to your installation.
See [Calculation modes](#calculation-modes) for all possible sensor configurations.

### Global configuration

All these settings are completely optional. You can skip this section if you don't need any advanced configuration.

| Name                          | Type    | Requirement  | Default                | Description                                                                                                                                        |
| ----------------------------- | ------- | ------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| enable_autodiscovery          | boolean | **Optional** | true                   | Whether you want powercalc to automatically setup power sensors for supported models in your HA instance.
| scan_interval                 | string  | **Optional** | 00:10:00               | Interval at which the sensor state is updated, even when the power value stays the same. Format HH:MM:SS                                           |
| create_energy_sensors         | boolean | **Optional** | true                   | Let the component automatically create energy sensors (kWh) for every power sensor                                                                 |
| power_sensor_naming           | string  | **Optional** | {} power               | Change the name of the sensors. Use the `{}` placeholder for the entity name of your appliance. This will also change the entity_id of your sensor |
| power_sensor_friendly_naming  | string  | **Optional** |                        | Change the friendly name of the sensors, Use `{}` placehorder for the original entity name. |
| power_sensor_category         | string  | **Optional** | Category for the created power sensors. See [generic-properties](https://developers.home-assistant.io/docs/core/entity/#generic-properties). |
| energy_sensor_naming          | string  | **Optional** | {} energy              | Change the name of the sensors. Use the `{}` placeholder for the entity name of your appliance. This will also change the entity_id of your sensor |
| energy_sensor_friendly_naming | string  | **Optional** |                        | Change the friendly name of the sensors, Use `{}` placehorder for the original entity name. |
| energy_sensor_category        | string  | **Optional** | Category for the created energy sensors. See [generic-properties](https://developers.home-assistant.io/docs/core/entity/#generic-properties). |
| create_utility_meters         | boolean | **Optional** | false                  | Set to `true` to automatically create utility meters of your energy sensors. See [utility_meters](#utility-meters) |
| utility_meter_types           | list    | **Optional** | daily, weekly, monthly | Define which cycles you want to create utility meters for. See [cycle](https://www.home-assistant.io/integrations/utility_meter/#cycle) |
| utility_meter_tariffs         | list    | **Optional** | Define different tariffs. See [tariffs](https://www.home-assistant.io/integrations/utility_meter/#tariffs). |
| energy_integration_method     | string  | **Optional** | trapezoid              | Integration method for the energy sensor. See [HA docs](https://www.home-assistant.io/integrations/integration/#method) |
| energy_sensor_precision       | numeric | **Optional** | 4                      | Number of decimals you want for the energy sensors. See [HA docs](https://www.home-assistant.io/integrations/integration/#round) |
| create_domain_groups          | list    | **Optional** |                        | Create grouped power sensor aggregating all powercalc sensors of given domains, see [Group sensors per domain](#group-sensors-per-domain)

**Example:**

```yaml
powercalc:
  scan_interval: 00:01:00 #Each minute
  power_sensor_naming: "{} Powersensor"
  create_energy_sensors: false
```

## Calculation modes

To calculate estimated power consumption different modes are supported, they are as follows:
- [LUT (lookup table)](#lut-mode)
- [Linear](#linear-mode)
- [Fixed](#fixed-mode)
- [WLED](#wled-mode)

### LUT mode
Supported domain: `light`

This is the most accurate mode.
For a lot of light models measurements are taken using smart plugs. All this data is saved into CSV files. When you have the LUT mode activated the current brightness/hue/saturation of the light will be checked and closest matching line will be looked up in the CSV.
- [Supported models](#supported-models) for LUT mode
- [LUT file structure](#lut-data-files)

#### Configuration

For most lights the device information in HA will supply the manufacturer and model correctly, so you only need to supply the `entity_id`.

```yaml
sensor:
  - platform: powercalc
    entity_id: light.livingroom_floorlamp
```

When the correct manufacturer and model could somehow not be discovered from HA, you can supply these manually.
You can also use this option to force using a certain LUT.

```yaml
sensor:
  - platform: powercalc
    entity_id: light.livingroom_floorlamp
    manufacturer: signify
    model: LCT010
```

Some light models (currently LIFX brand) require you to refer a so called "sub LUT" directory, because they have different power charasteristics based on some information not known to HA. For example LIFX BR30 Night Vision light has some infrared mode which can be set in the LIFX app. To load the LUT file for a infrared setting of 25 you can use the following configuration.
You will need to lookup the available sub LUT's in the powercalc data directory.

```yaml
sensor:
  - platform: powercalc
    entity_id: light.lifx_nighvision
    manufacturer: lifx
    model: LIFX BR30 Night Vision/infrared_25
```

### Linear mode
Supported domains: `light`, `fan`

The linear mode can be used for dimmable devices which don't have a lookup table available.
You need to supply the min and max power draw yourself, by either looking at the datasheet or measuring yourself with a smart plug / power meter.
Power consumpion is calculated by ratio. So when you have your fan running at 50% speed and define watt range 2 - 6, than the estimated consumption will be 4 watt.

#### Configuration options
| Name              | Type    | Requirement  | Description                                 |
| ----------------- | ------- | ------------ | ------------------------------------------- |
| attribute         | string  | **Optional** | State attribute to use for the linear range. When not supplied will be `brightness` for lights, and `percentage` for fans     |
| min_power         | float   | **Optional** | Power usage for lowest brightness level     |
| max_power         | float   | **Optional** | Power usage for highest brightness level    |
| calibrate         | string  | **Optional** | Calibration values                          |
| gamma_curve       | float   | **Optional** | Apply a gamma correction, for example 2.8   |

#### Example configuration

```yaml
sensor:
  - platform: powercalc
    entity_id: light.livingroom_floorlamp
    linear:
      min_power: 0.5
      max_power: 8
```

> Note: defining only `min_power` and `max_power` is only allowed for light and fan entities, when you are using another entity (for example a `sensor` or `input_number`) you must use the calibrate mode.

#### Advanced precision calibration

With the `calibrate` setting you can supply more than one power value for multiple brightness/percentage levels.
This allows for a more accurate estimation because not all lights are straight linear.

Also you can use this calibration table for other entities than lights and fans, to supply the state values and matching power values.

```yaml
sensor:
  - platform: powercalc
    entity_id: light.livingroom_floorlamp
    linear:
      calibrate:
        - 1 -> 0.3
        - 10 -> 1.25
        - 50 -> 3.50
        - 100 -> 6.8
        - 255 -> 15.3
```

> Note: For lights the supplied values must be in brightness range 1-255, when you select 1 in lovelace UI slider this is actually brightness level 3.
> For fan speeds the range is 1-100 (percentage)

Configuration with an sensor (`sensor.heater_modulation`) which supplies a percentage value (1-100):

```yaml
sensor:
  - platform: powercalc
    entity_id: sensor.heater_modulation
    name: Heater
    linear:
      calibrate:
        - 1 -> 200
        - 100 -> 1650
```

You could also use this to setup a power profile for your robot vacuum cleaner, which only consumes power when it is docked into the charching port. You need to use this in conjunction with the `calculation_enabled_condition` to only activate the power calculation when the device is docked.

```yaml
- platform: powercalc
  entity_id: vacuum.my_robot_cleaner
  calculation_enabled_condition: "{{ is_state('vacuum.my_robot_cleaner', 'docked') }}"
  linear:
    attribute: battery_level
    calibrate:
        - 1 -> 20
        - 79 -> 20
        - 80 -> 15
        - 99 -> 8
        - 100 -> 1.5
```

### Fixed mode
Supported domains: `light`, `fan`, `humidifier`, `switch`, `binary_sensor`, `device_tracker`, `remote`, `media_player`, `input_boolean`, `input_number`, `input_select`, `sensor`, `climate`, `vacuum`, `water_heater`

When you have an appliance which only can be set on and off you can use this mode.
You need to supply a single watt value in the configuration which will be used when the device is ON

#### Configuration options
| Name              | Type    | Requirement  | Description                                           |
| ----------------- | ------- | ------------ | ----------------------------------------------------- |
| power             | float   | **Optional** | Power usage when the appliance is turned on (in watt). Can also be a [template](https://www.home-assistant.io/docs/configuration/templating/) |
| states_power      | dict    | **Optional** | Power usage per entity state. Values can also be a [template](https://www.home-assistant.io/docs/configuration/templating/) |

#### Simple example
```yaml
sensor:
  - platform: powercalc
    entity_id: light.nondimmabled_bulb
    fixed:
      power: 20
```

#### Using a template for the power value
```yaml
sensor:
  - platform: powercalc
    entity_id: light.bathroom
    fixed:
      power: "{{states('input_number.bathroom_watts')}}"
```

#### Power per state
The `states_power` setting allows you to specify a power per entity state. This can be useful for example on Sonos devices which have a different power consumption in different states.

```yaml
sensor:
  - platform: powercalc
    entity_id: media_player.sonos_living
    fixed:
      states_power:
        playing: 8.3
        paused: 2.25
        idle: 1.5
```

You can also use state attributes. Use the `|` delimiter to seperate the attribute and value. Here is en example:

```yaml
sensor:
  - platform: powercalc
    entity_id: media_player.sonos_living
    fixed:
      power: 12
      states_power:
        media_content_id|Spotify: 5
        media_content_id|Youtube: 10
```

When no match is found in `states_power` lookup than the configured `power` will be considered.

### WLED mode
Supported domains: `light`

You can use WLED strategy for light strips which are controlled by [WLED](https://github.com/Aircoookie/WLED).
WLED calculates estimated current based on brightness levels and the microcontroller (ESP) used.
Powercalc asks to input the voltage on which the lightstrip is running and optionally a power factor. Based on these factors the wattage is calculated.

#### Configuration options
| Name              | Type    | Requirement  | Default | Description                                 |
| ----------------- | ------- | ------------ | ------- | ------------------------------------------- |
| voltage           | float   | **Required** |         | Voltage for the lightstrip                  |
| power_factor      | float   | **Optional** | 0.9     | Power factor, between 0.1 and 1.0           |

#### Example configuration

```yaml
sensor:
  - platform: powercalc
    entity_id: light.wled_lightstrip
    wled:
      voltage: 5
```

## More configuration examples

### Linear mode with additional standby power

```yaml
sensor:
  - platform: powercalc
    entity_id: light.livingroom_floorlamp
    linear:
      min_power: 0.5
      max_power: 8
    standby_power: 0.2
    name: My amazing power meter
```

## Daily fixed energy

> Available from v0.13 an higher

Sometimes you want to keep track of energy usage of individual devices which are not managed by Home Assistant.
When you know the energy consumption in kWh or W powercalc can make it possible to create an energy sensor (which can also be used in the energy dashboard). 
This can be helpful for devices which are always on and have a relatively fixed power draw. For example an IP camera, intercom, Google nest, Alexa, network switches etc.

### Configuration options

| Name                | Type    | Requirement  | Default  | Description                                           |
| ------------------- | ------- | ------------ | -------- | ------------------------------------------- |
| value               | float   | **Required** |          | Value either in watts or kWh. Can also be a [template](https://www.home-assistant.io/docs/configuration/templating/) |
| unit_of_measurement | string  | **Optional** | kWh      | `kWh` or `W` |
| on_time             | period  | **Optional** | 24:00:00 | How long the device is on per day. Only applies when unit_of_measurement is set to `W`. Format HH:MM:SS |
| update_frequency    | integer | **Optional** | 1800     | Seconds between each increase in kWh |

### Configuration examples

This will add 0.05 kWh per day to the energy sensor called "IP camera upstairs"

```yaml
sensor:
  - platform: powercalc
    name: IP camera upstairs
    daily_fixed_energy:
      value: 0.05
```

Or define in watts, with an optional on time (which is 24 hour a day by default).

```yaml
sensor:
  - platform: powercalc
    name: Intercom
    daily_fixed_energy:
      value: 21
      unit_of_measurement: W
      on_time: 12:00:00
```

This will simulate the device using 21 watts for 12 hours a day. The energy sensor will increase by 0.252 kWh a day.

## Sensor naming

Let's assume you have a source sensor `light.patio` with name "Patio".
Powercalc will create the following sensors by default.
- sensor.patio_power (Patio power)
- sensor.patio_energy (Patio energy)

> Utility meters will use the energy name as a base and suffix with `_daily`, `_weekly`, `_monthly`

### Change suffixes
To change the default suffixes `_power` and `_energy` you can use the `power_sensor_naming` and `energy_sensor_naming` options.
The following configuration:

```yaml
powercalc:
  energy_sensor_naming: "{} kWh consumed"
```

will create:
- sensor.patio_power (Patio power)
- sensor.patio_kwh_consumed (Patio kWh consumed)

### Change name
You can also change the sensor name with the `name` option

```yaml
sensor:
  - platform: powercalc
    entity_id: light.patio
    name: Patio Light
```

will create:
- sensor.patio_light_power (Patio light power)
- sensor.patio_light_energy (Patio light energy)

## Setting up for energy dashboard
If you want to use the virtual power sensors with the new [energy integration](https://www.home-assistant.io/blog/2021/08/04/home-energy-management/), you have to create an energy sensor which utilizes the power of the powercalc sensor. Starting from v0.4 of powercalc it will automatically create energy sensors for you by default. No need for any custom configuration. These energy sensors then can be selected in the energy dashboard. 

You can disable the automatic creation of energy sensors with the option `create_energy_sensors` in your configuration (see [global configuration](#global-configuration)).

> Note: It can take some time for the energy sensors to appear in the energy dashboard, sometimes more than an hour. Just have a little patience ;-)

## Advanced features

### Multiple entities and grouping

Powercalc provides multiple possibilities to create aggregate group sensors, which sums the power of multiple individual power sensors into one sensor.

- Group sensors per domain
- Manually defining groups by configuration

#### Group sensors per domain

> Available from v0.19 and higher

Powercalc makes it easy to create a group sensors for all entities of a given domain with the `create_domain_groups` option. For example let's assume you want group sensors for all your lights and media players you can use the following configuration.

```yaml
powercalc:
  create_domain_groups:
    - light
    - media_player
```

#### Manually defining groups by configuration

> Available from v0.8 and higher

`entities` will allow you to multiple power sensors in one `powercalc` sensor entry.
`create_group` will also create a group summing all the underlying entities. Which can directly be used in energy dashboard.
Each entry under `entities` can use the same configuration as when defined directly under `sensor`

```yaml
sensor:
  - platform: powercalc
    create_group: All hallway lights
    entities:
      -  entity_id: light.hallway
      -  entity_id: light.living_room
         linear:
           min_power: 0.5
           max_power: 8
```

This will create the following entities:
- sensor.hallway_power
- sensor.hallway_energy
- sensor.living_room_power
- sensor.living_room_energy
- sensor.all_hallway_lights_power (group sensor)
- sensor.all_hallway_lights_energy (group sensor)

**Nesting groups**
> Available from v0.15 and higher

You can also nest groups, this makes it possible to add an entity to multiple groups.

```yaml
sensor:
  - platform: powercalc
    create_group: All lights
    entities:
      - entity_id: light.a
      - entity_id: light.b
      - create_group: Upstairs lights
        entities:
          - entity_id: light.c
          - create_group: Bedroom Bob lights
            entities:
              - entity_id: light.d
```

Each group will have power sensors created for the following lights:
- All lights: `light.a`, `light.b`, `light.c`, `light.d`
- Upstairs lights: `light.c`, `light.d`
- Bedroom Bob lights: `light.d`

> Note: a maximum nesting level of 5 groups is allowed!

##### Dynamically including entities

Powercalc provides several methods to automatically include a bunch of entities in a group with the `include` option.
> Note: only entities will be included which are in the supported models list (these can be auto configured). You can combine `include` and `entities` to extend the group with custom configured entities.

**Include area**

> Available from v0.12 and higher

```yaml
sensor:
  - platform: powercalc
    create_group: Outdoor
    include:
      area: outdoor
```

This can also be mixed with the `entities` option, to add or override entities to the group. i.e.

```yaml
sensor:
  - platform: powercalc
    create_group: Outdoor
    include:
      area: outdoor
    entities:
      - entity_id: light.frontdoor
        fixed:
          power: 100
```

**Include group**

> Available from v0.14 and higher

Includes entities from a Home Assistant [group](https://www.home-assistant.io/integrations/group/) or [light group](https://www.home-assistant.io/integrations/light.group/)

```yaml
sensor:
  - platform: powercalc
    create_group: Livingroom lights
    include:
      group: group.livingroom_lights
```

**Include template**

> Available from v0.14 and higher

```yaml
sensor:
  - platform: powercalc
    create_group: All indoor lights
    include:
      template: {{expand('group.all_indoor_lights')|map(attribute='entity_id')|list}}
```

**Include domain**

> Available from v0.19 and higher

```yaml
sensor:
  - platform: powercalc
    create_group: All lights
    include:
      domain: light
```

### Multiply Factor

This feature allows you to multiply the calculated power.

This can be useful in the following use cases:
- You have a bunch of similar lights which you control as a group and want a single power sensor.
- You are using a LED strip from the LUT models, but you have extended or shortened it.

Let's assume you have a combination of 4 GU10 spots in your ceiling in a light group `light.livingroom_spots`

```yaml
- platform: powercalc
  entity_id: light.livingroom_spots
  multiply_factor: 4
```

This will add the power sensor `sensor.livingroom_spots_power` and the measured power will be multiplied by 4, as the original measurements are for 1 spot.

By default the multiply factor will **NOT** be applied to the standby power, you can set the `multiply_factor_standby` to do this.

```yaml
- platform: powercalc
  entity_id: light.livingroom_spots
  multiply_factor: 4
  multiply_factor_standby: true
```

> Note: a multiply_factor lower than 1 will decrease the power. For example 0.5 will half the power.

### Utility meters

The energy sensors created by the component will keep increasing the total kWh, and never reset.
When you want to know the energy consumed the last 24 hours, or last month you can use the [utility_meter](https://www.home-assistant.io/integrations/utility_meter/) component of Home Assistant. Powercalc allows you to automatically create utility meters for all your powercalc sensors with a single line of configuration.

```yaml
powercalc:
  create_utility_meters: true
```

By default utility meters are created for `daily`, `weekly`, `monthly` cycles.
You can change this behaviour with the `utility_meter_types` configuration option.

```yaml
powercalc:
  create_utility_meters: true
  utility_meter_types:
    - daily
    - yearly
```

The utility meters have the same name as your energy sensor, but are extended by the meter cycle.
Assume you have a light `light.floorlamp_livingroom`, than you should have the following sensors created:
- `sensor.floorlamp_livingroom_power`
- `sensor.floorlamp_livingroom_energy`
- `sensor.floorlamp_livingroom_energy_daily`
- `sensor.floorlamp_livingroom_energy_weekly`
- `sensor.floorlamp_livingroom_energy_monthly`

### Use real power sensor

> Available from v0.14 and higher

Use the following configuration to use an existing power sensor and let powercalc create the energy sensors and utility meters for it:

```yaml
- platform: powercalc
  entity_id: light.toilet
  power_sensor_id: sensor.toilet_light_power
```

### Resetting energy sensor

> Available from v0.23 and higher

Powercalc provides a service `powercalc.reset_energy` which you can call to reset energy sensors to 0 kWh.
You can call this service from the GUI (Developer tools -> Services)

<hr>

## Light model library

The component ships with predefined light measurements for some light models.
This library will keep extending by the effort of community users.

These models are located in `config/custom_components/powercalc/data` directory. 
You can also define your own models in `config/powercalc-custom-models` directory, when a manufacturer/model exists in this directory this will take precedence over the default data directory.

Each light model has it's own subdirectory `{manufacturer}/{modelid}`. i.e. signify/LCT010

### model.json

Every model MUST contain a `model.json` file which defines the supported calculation modes and other configuration.
See the [json schema](custom_components/powercalc/data/model_schema.json) how the file must be structured or the examples below.

When [LUT mode](#lut-mode) is supported also [CSV lookup files](#lut-data-files) must be provided.

Example lut mode:

```json
{
    "name": "Hue White and Color Ambiance A19 E26 (Gen 5)",
    "standby_power": 0.4,
    "supported_modes": [
        "lut"
    ],
    "measure_method": "script",
    "measure_device": "Shelly Plug S"
}
```

Example linear mode

```json
{
    "name": "Hue Go",
    "supported_modes": [
        "linear"
    ],
    "standby_power": 0.2,
    "linear_config": {
        "min_power": 0,
        "max_power": 6
    },
    "measure_method": "manual",
    "measure_device": "From manufacturer specifications"
}
```

### LUT data files

To calculate power consumption a lookup is done into CSV data files.

Depending on the supported color modes of the light the integration expects one or more CSV files here:
 - hs.csv.gz (hue/saturation, colored lamps)
 - color_temp.csv.gz (color temperature)
 - brightness.csv.gz (brightness only lights)

Some lights support two color modes (both hs and color_temp), so there must be two CSV files.

The files are gzipped to keep the repository footprint small, and installation fast but gzipping files is not mandatory.

Example:

```
- signify
  - LCT010
    - model.json
    - hs.csv.gz
    - color_temp.csv.gz
```

#### Expected file structure

- The file **MUST** contain a header row.
- The data rows in the CSV files **MUST** have the following column order:

**hs.csv**
```csv
bri,hue,sat,watt
```

**color_temp.csv**
```csv
bri,mired,watt
```

**brightness.csv**
```csv
bri,watt
```

***Ranges***:
- brightness (0-255)
- hue (0-65535)
- saturation (0-255)
- mired (0-500)  min value depending on min mired value of the light model

#### Creating LUT files

New files are created by taking measurements using a smartplug (i.e. Shelly plug) and changing the light to all kind of different variations using the Hue API or Home Assistant API.
The tooling is available at `utils/measure`.

The script supports several smartplugs with power monitoring.

See the [README](utils/measure/README.md) for more information.

### Supported models

See the [list](docs/supported_models.md) of supported lights which don't need any manual configuration

Yeelights may not autodetect by default, [see here](docs/yeelight.md) for additional steps to configure them.

## Debug logging

Add the following to configuration.yaml:

```yaml
logger:
  default: warning
  logs:
    custom_components.powercalc: debug
```

INFO.md METASEP
# :zap: PowerCalc: Home Assistant Virtual Power Sensors
Custom component to calculate estimated power consumption of lights and other appliances.
Provides easy configuration to get virtual power consumption sensors in Home Assistant for all your devices which don't have a build in power meter.
This component estimates power usage by looking at brightness, hue/saturation and color temperature etc using different strategies. Other devices than lights are supported, you'll need to manually configure those.
Also possibilities are available to easily put all your energy sensors in groups, for example everything in the living room.

![Preview](https://raw.githubusercontent.com/bramstroker/homeassistant-powercalc/master/assets/preview.gif)

[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/bramski)

## Post installation steps
- Restart HA
- Add the following entry to `configuration.yaml`:
```yaml
powercalc:
```
- Restart HA final time

## Setup power sensors

After restarting power and energy sensors should appear for the lights found in your HA installation which are supported by powercalc. 
Please see the list of [supported models](https://github.com/bramstroker/homeassistant-powercalc/blob/master/docs/supported_models.md).
When no power sensor is appearing please check the logs for any errors.
Powercalc also provides extensive configuation to setup your own power sensors using different strategies. Please see the main [Documentation](https://github.com/bramstroker/homeassistant-powercalc/blob/master/README.md) on github for all the options and examples.
Also see the [WiKi](https://github.com/bramstroker/homeassistant-powercalc/wiki) for even more information and the FAQ

## Debug logging

Add the following to configuration.yaml:

```yaml
logger:
  default: warning
  logs:
    custom_components.powercalc: debug
```

custom_components/powercalc/data/model_schema.json METASEP
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "model.json described a light model",
  "type": "object",
  "required": [
    "name",
    "standby_power",
    "measure_method",
    "measure_device"
  ],
  "properties": {
    "name": {
      "type": "string",
      "description": "The full name"
    },
    "standby_power": {
      "type": "number",
      "minimum": 0.05,
      "description": "Power draw when the light is turned of. When you are not able to measure set to 0.4"
    },
    "standby_power_on": {
      "type": "number",
      "minimum": 0.05,
      "description": "Power draw when the light is turned on."
    },
    "supported_modes": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": ["lut", "linear", "fixed"]
      },
      "description": "Supported calculation modes"
    },
    "measure_method": {
      "type": "string",
      "enum": ["manual", "script"],
      "description": "How the light was measured"
    },
    "measure_device": {
      "type": "string",
      "description": "Device which was used to measure"
    },
    "measure_description": {
      "type": "string",
      "description": "Add more information about how you measured the light or any remarks"
    },
    "measure_settings": {
      "type": "object",
      "description": "Settings used for measure script"
    },
    "linked_lut": {
      "type": "string",
      "description": "Use LUT data files from another model"
    },
    "requires_additional_configuration": {
      "type": "boolean",
      "description": "Defines whether this model needs additional configuration by the user. Used for smart switches and lights where we can only know the standby power, but not the power of connected light bulb"
    },
    "device_type": {
      "type": "string",
      "enum": ["light", "smart_switch"],
      "description": "Type of device"
    },
    "calculation_enabled_condition": {
      "type": "string",
      "description": "Template which can be evaluated."
    },
    "linear_config": {
      "type": "object",
      "description": "Configuration for linear calculation mode",
      "properties": {
        "min_watt": {
          "type": "number"
        },
        "max_watt": {
          "type": "number"
        }
      }
    },
    "fixed_config": {
      "type": "object",
      "description": "Configuration for fixed calculation mode",
      "properties": {
        "watt": {
          "type": "number"
        }
      }
    }
  }
}
custom_components/powercalc/strategy/wled.py METASEP
from __future__ import annotations

import logging
from decimal import Decimal
from typing import Optional

import voluptuous as vol
from homeassistant.core import State
from homeassistant.helpers import entity_registry
from homeassistant.helpers.event import TrackTemplate
from homeassistant.helpers.typing import HomeAssistantType

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import (
    CONF_POWER_FACTOR,
    CONF_VOLTAGE,
    OFF_STATES,
)
from custom_components.powercalc.errors import StrategyConfigurationError
from custom_components.powercalc.helpers import evaluate_power

from .strategy_interface import PowerCalculationStrategyInterface

CONFIG_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_VOLTAGE): vol.Coerce(float),
        vol.Optional(CONF_POWER_FACTOR, default=0.9): vol.Coerce(float),
    }
)

_LOGGER = logging.getLogger(__name__)


class WledStrategy(PowerCalculationStrategyInterface):
    def __init__(
        self,
        config: dict,
        light_entity: SourceEntity,
        hass: HomeAssistantType,
        standby_power: Optional[float],
    ) -> None:
        self._hass = hass
        self._voltage = config.get(CONF_VOLTAGE)
        self._power_factor = config.get(CONF_POWER_FACTOR) or 0.9
        self._light_entity = light_entity
        self._standby_power = standby_power

    async def calculate(self, entity_state: State) -> Optional[Decimal]:
        if entity_state.entity_id == self._light_entity.entity_id:
            light_state = entity_state.state
        else:
            light_state = self._hass.states.get(self._light_entity.entity_id)

        if light_state.state in OFF_STATES and self._standby_power:
            return self._standby_power

        if entity_state.entity_id != self._estimated_current_entity:
            return None

        _LOGGER.debug(
            f"{self._light_entity.entity_id}: Estimated current {entity_state.state} (voltage={self._voltage}, power_factor={self._power_factor})"
        )
        power = float(entity_state.state) / 1000 * self._voltage * self._power_factor
        return await evaluate_power(power)

    async def find_estimated_current_entity(self) -> str:
        entity_reg = entity_registry.async_get(self._hass)
        entity_id = f"sensor.{self._light_entity.object_id}_estimated_current"
        entry = entity_reg.async_get(entity_id)
        if entry:
            return entry.entity_id

        device_id = self._light_entity.entity_entry.device_id
        estimated_current_entities = [
            entity_entry.entity_id
            for entity_entry in entity_registry.async_entries_for_device(
                entity_reg, device_id
            )
            if "estimated_current" in entity_entry.entity_id
        ]
        if estimated_current_entities:
            return estimated_current_entities[0]

        raise StrategyConfigurationError("{No estimated current entity found")

    def get_entities_to_track(self) -> list[str, TrackTemplate]:
        return [self._estimated_current_entity]

    def can_calculate_standby(self) -> bool:
        return True

    async def validate_config(self, source_entity: SourceEntity):
        self._estimated_current_entity = await self.find_estimated_current_entity()

custom_components/powercalc/strategy/strategy_interface.py METASEP
from decimal import Decimal
from typing import Optional

from homeassistant.core import State
from homeassistant.helpers.event import TrackTemplate

from custom_components.powercalc.common import SourceEntity


class PowerCalculationStrategyInterface:
    async def calculate(self, entity_state: State) -> Optional[Decimal]:
        """Calculate power consumption based on entity state"""
        pass

    async def validate_config(self, source_entity: SourceEntity):
        """Validate correct setup of the strategy"""
        pass

    def get_entities_to_track(self) -> list[str, TrackTemplate]:
        return []

    def can_calculate_standby(self) -> bool:
        return False

custom_components/powercalc/strategy/lut.py METASEP
from __future__ import annotations

import gzip
import logging
import os
from collections import defaultdict
from csv import reader
from dataclasses import dataclass
from decimal import Decimal
from functools import partial
from typing import Optional, Union

import numpy as np
from homeassistant.components import light
from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_MODE,
    ATTR_COLOR_TEMP,
    ATTR_HS_COLOR,
    COLOR_MODE_BRIGHTNESS,
    COLOR_MODE_COLOR_TEMP,
    COLOR_MODE_HS,
    COLOR_MODE_UNKNOWN,
    COLOR_MODES_COLOR,
)
from homeassistant.core import State

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.errors import (
    LutFileNotFound,
    ModelNotSupported,
    StrategyConfigurationError,
    UnsupportedMode,
)
from custom_components.powercalc.light_model import LightModel

from .strategy_interface import PowerCalculationStrategyInterface

LUT_COLOR_MODES = {COLOR_MODE_BRIGHTNESS, COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS}

_LOGGER = logging.getLogger(__name__)


class LutRegistry:
    def __init__(self) -> None:
        self._lookup_dictionaries = {}

    async def get_lookup_dictionary(
        self, light_model: LightModel, color_mode: str
    ) -> dict | None:
        cache_key = f"{light_model.manufacturer}_{light_model.model}_{color_mode}"
        lookup_dict = self._lookup_dictionaries.get(cache_key)
        if lookup_dict is None:
            defaultdict_of_dict = partial(defaultdict, dict)
            lookup_dict = defaultdict(defaultdict_of_dict)

            with self.get_lut_file(light_model, color_mode) as csv_file:
                csv_reader = reader(csv_file)
                next(csv_reader)  # skip header row

                line_count = 0
                for row in csv_reader:
                    if color_mode == COLOR_MODE_HS:
                        lookup_dict[int(row[0])][int(row[1])][int(row[2])] = float(
                            row[3]
                        )
                    elif color_mode == COLOR_MODE_COLOR_TEMP:
                        lookup_dict[int(row[0])][int(row[1])] = float(row[2])
                    elif color_mode == COLOR_MODE_BRIGHTNESS:
                        lookup_dict[int(row[0])] = float(row[1])
                    else:
                        raise UnsupportedMode(f"Unsupported color mode {color_mode}")
                    line_count += 1

            _LOGGER.debug("LUT file loaded: %d lines", line_count)

            lookup_dict = dict(lookup_dict)
            self._lookup_dictionaries[cache_key] = lookup_dict

        return lookup_dict

    def get_lut_file(self, light_model: LightModel, color_mode: str):
        path = os.path.join(light_model.get_lut_directory(), f"{color_mode}.csv")

        gzip_path = f"{path}.gz"
        if os.path.exists(gzip_path):
            _LOGGER.debug("Loading LUT data file: %s", gzip_path)
            return gzip.open(gzip_path, "rt")

        elif os.path.exists(path):
            _LOGGER.debug("Loading LUT data file: %s", path)
            return open(path, "r")

        raise LutFileNotFound("Data file not found: %s")


class LutStrategy(PowerCalculationStrategyInterface):
    def __init__(self, lut_registry: LutRegistry, model: LightModel) -> None:
        self._lut_registry = lut_registry
        self._model = model

    async def calculate(self, entity_state: State) -> Optional[Decimal]:
        """Calculate the power consumption based on brightness, mired, hsl values."""
        attrs = entity_state.attributes
        color_mode = attrs.get(ATTR_COLOR_MODE)
        if color_mode in COLOR_MODES_COLOR:
            color_mode = COLOR_MODE_HS

        brightness = attrs.get(ATTR_BRIGHTNESS)
        if brightness is None:
            _LOGGER.error(
                "%s: Could not calculate power. no brightness set",
                entity_state.entity_id,
            )
            return None
        if brightness > 255:
            brightness = 255

        if color_mode is COLOR_MODE_UNKNOWN:
            _LOGGER.debug(
                "%s: Could not calculate power. color mode unknown",
                entity_state.entity_id,
            )
            return None

        try:
            lookup_table = await self._lut_registry.get_lookup_dictionary(
                self._model, color_mode
            )
        except LutFileNotFound:
            _LOGGER.error(
                "%s: Lookup table not found (model: %s, color_mode: %s)",
                entity_state.entity_id,
                self._model.model,
                color_mode,
            )
            return None

        power = 0
        light_setting = LightSetting(color_mode=color_mode, brightness=brightness)
        if color_mode == COLOR_MODE_HS:
            hs = attrs[ATTR_HS_COLOR]
            light_setting.hue = int(hs[0] / 360 * 65535)
            light_setting.saturation = int(hs[1] / 100 * 255)
            _LOGGER.debug(
                "%s: Looking up power usage for bri:%s hue:%s sat:%s}",
                entity_state.entity_id,
                brightness,
                light_setting.hue,
                light_setting.saturation,
            )
        elif color_mode == COLOR_MODE_COLOR_TEMP:
            light_setting.color_temp = attrs[ATTR_COLOR_TEMP]
            _LOGGER.debug(
                "%s: Looking up power usage for bri:%s mired:%s",
                entity_state.entity_id,
                brightness,
                light_setting.color_temp,
            )
        elif color_mode == COLOR_MODE_BRIGHTNESS:
            _LOGGER.debug(
                "%s: Looking up power usage for bri:%s",
                entity_state.entity_id,
                brightness,
            )

        power = Decimal(self.lookup_power(lookup_table, light_setting))
        _LOGGER.debug("%s: Calculated power:%s", entity_state.entity_id, power)
        return power

    def lookup_power(self, lookup_table: dict, light_setting: LightSetting) -> float:
        brightness = light_setting.brightness
        brightness_table = lookup_table.get(brightness)

        # Check if we have an exact match for the selected brightness level in de LUT
        if brightness_table:
            return self.lookup_power_for_brightness(brightness_table, light_setting)

        # We don't have an exact match, use interpolation
        brightness_range = [
            self.get_nearest_lower_brightness(lookup_table, brightness),
            self.get_nearest_higher_brightness(lookup_table, brightness),
        ]
        power_range = [
            self.lookup_power_for_brightness(
                lookup_table[brightness_range[0]], light_setting
            ),
            self.lookup_power_for_brightness(
                lookup_table[brightness_range[1]], light_setting
            ),
        ]
        return np.interp(brightness, brightness_range, power_range)

    def lookup_power_for_brightness(
        self, lut_value: Union(dict, int), light_setting: LightSetting
    ):
        if light_setting.color_mode == COLOR_MODE_BRIGHTNESS:
            return lut_value
        if light_setting.color_mode == COLOR_MODE_COLOR_TEMP:
            return self.get_nearest(lut_value, light_setting.color_temp)
        else:
            sat_values = self.get_nearest(lut_value, light_setting.hue)
            return self.get_nearest(sat_values, light_setting.saturation)

    def get_nearest(self, dict: dict, search_key: int):
        return (
            dict.get(search_key)
            or dict[min(dict.keys(), key=lambda key: abs(key - search_key))]
        )

    def get_nearest_lower_brightness(self, dict: dict, search_key: int) -> int:
        keys = dict.keys()
        last_key = [*keys][-1]
        if last_key < search_key:
            return last_key

        return max(
            (k for k in dict.keys() if int(k) <= int(search_key)), default=[*keys][0]
        )

    def get_nearest_higher_brightness(self, dict: dict, search_key: int) -> int:
        keys = dict.keys()
        first_key = [*keys][0]
        if first_key > search_key:
            return first_key

        return min((k for k in keys if int(k) >= int(search_key)), default=[*keys][-1])

    async def validate_config(self, source_entity: SourceEntity):
        if source_entity.domain != light.DOMAIN:
            raise StrategyConfigurationError("Only light entities can use the LUT mode")

        if self._model.manufacturer is None:
            _LOGGER.error(
                "Manufacturer not supplied for entity: %s", source_entity.entity_id
            )

        if self._model.model is None:
            _LOGGER.error("Model not supplied for entity: %s", source_entity.entity_id)
            return

        for color_mode in source_entity.supported_color_modes:
            if color_mode in LUT_COLOR_MODES:
                try:
                    await self._lut_registry.get_lookup_dictionary(
                        self._model, color_mode
                    )
                except LutFileNotFound:
                    raise ModelNotSupported("No lookup file found for mode", color_mode)


@dataclass
class LightSetting:
    color_mode: str
    brightness: int
    hue: Optional[int] = None
    saturation: Optional[int] = None
    color_temp: Optional[int] = None

custom_components/powercalc/strategy/linear.py METASEP
from __future__ import annotations

import logging
from decimal import Decimal
from typing import Optional

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components import fan, light
from homeassistant.components.fan import ATTR_PERCENTAGE
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.const import CONF_ATTRIBUTE
from homeassistant.core import State
from homeassistant.helpers.typing import HomeAssistantType

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import (
    CONF_CALIBRATE,
    CONF_GAMMA_CURVE,
    CONF_MAX_POWER,
    CONF_MIN_POWER,
)
from custom_components.powercalc.errors import StrategyConfigurationError

from .strategy_interface import PowerCalculationStrategyInterface

ALLOWED_DOMAINS = [fan.DOMAIN, light.DOMAIN]
CONFIG_SCHEMA = vol.Schema(
    {
        vol.Optional(CONF_CALIBRATE): vol.All(
            cv.ensure_list, [vol.Match("^[0-9]+ -> ([0-9]*[.])?[0-9]+$")]
        ),
        vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
        vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
        vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
        vol.Optional(CONF_ATTRIBUTE): cv.string,
    }
)

_LOGGER = logging.getLogger(__name__)


class LinearStrategy(PowerCalculationStrategyInterface):
    def __init__(
        self,
        config: dict,
        hass: HomeAssistantType,
        source_entity: SourceEntity,
        standby_power: Optional[float],
    ) -> None:
        self._config = config
        self._hass = hass
        self._source_entity = source_entity
        self._standby_power = standby_power
        self._calibration = self.create_calibrate_list()

    async def calculate(self, entity_state: State) -> Optional[Decimal]:
        """Calculate the current power consumption"""
        value = self.get_current_state_value(entity_state)

        min_calibrate = self.get_min_calibrate(value)
        max_calibrate = self.get_max_calibrate(value)
        min_value = min_calibrate[0]
        max_value = max_calibrate[0]

        _LOGGER.debug(
            f"{self._source_entity.entity_id}: Linear mode state value: {value} range({min_value}-{max_value})"
        )
        if value is None:
            return None

        min_power = min_calibrate[1]
        max_power = max_calibrate[1]

        value_range = max_value - min_value
        if value_range == 0:
            return Decimal(min_power)

        power_range = max_power - min_power

        gamma_curve = self._config.get(CONF_GAMMA_CURVE) or 1

        relative_value = (value - min_value) / value_range

        power = power_range * relative_value**gamma_curve + min_power

        return Decimal(power)

    def get_min_calibrate(self, value: int) -> tuple[int, float]:
        """Get closest lower value from calibration table"""
        return min(self._calibration, key=lambda v: (v[0] > value, value - v[0]))

    def get_max_calibrate(self, value: int) -> tuple[int, float]:
        """Get closest higher value from calibration table"""
        return max(self._calibration, key=lambda v: (v[0] > value, value - v[0]))

    def create_calibrate_list(self) -> list[tuple]:
        """Build a table of calibration values"""
        list = []

        calibrate = self._config.get(CONF_CALIBRATE)
        if calibrate is None:
            if not self._source_entity.domain in ALLOWED_DOMAINS:
                return list
            full_range = self.get_entity_value_range()
            min = full_range[0]
            max = full_range[1]
            min_power = self._config.get(CONF_MIN_POWER) or self._standby_power or 0
            list.append((min, float(min_power)))
            list.append((max, float(self._config.get(CONF_MAX_POWER))))
            return list

        for line in calibrate:
            parts = line.split(" -> ")
            list.append((int(parts[0]), float(parts[1])))

        sorted_list = sorted(list, key=lambda tup: tup[0])
        return sorted_list

    def get_entity_value_range(self) -> tuple:
        """Get the min/max range for a given entity domain"""
        if self._source_entity.domain == fan.DOMAIN:
            return (0, 100)

        if self._source_entity.domain == light.DOMAIN:
            return (0, 255)

    def get_current_state_value(self, entity_state: State) -> Optional[int]:
        """Get the current entity state, i.e. selected brightness"""

        attribute = self.get_attribute(entity_state)
        if attribute:
            value = entity_state.attributes.get(attribute)
            if value is None:
                _LOGGER.error(
                    f"No {attribute} attribute for entity: {entity_state.entity_id}"
                )
                return None
            if attribute == ATTR_BRIGHTNESS and value > 255:
                value = 255
            return value

        try:
            return int(float(entity_state.state))
        except ValueError as e:
            _LOGGER.error(
                f"Expecting state to be a number for entity: {entity_state.entity_id}"
            )
            return None

    def get_attribute(self, entity_state: State) -> str | None:
        """Returns the attribute which needs to be read for the linear calculation"""

        if CONF_ATTRIBUTE in self._config:
            return self._config.get(CONF_ATTRIBUTE)

        if entity_state.domain == light.DOMAIN:
            return ATTR_BRIGHTNESS

        if entity_state.domain == fan.DOMAIN:
            return ATTR_PERCENTAGE

        return None

    async def validate_config(self, source_entity: SourceEntity):
        """Validate correct setup of the strategy"""

        if not CONF_CALIBRATE in self._config:
            if source_entity.domain not in ALLOWED_DOMAINS:
                raise StrategyConfigurationError(
                    "Entity domain not supported for linear mode. Must be one of: {}, or use the calibrate option".format(
                        ",".join(ALLOWED_DOMAINS)
                    )
                )
            if not CONF_MAX_POWER in self._config:
                raise StrategyConfigurationError(
                    "Linear strategy must have at least 'max power' or 'calibrate' defined"
                )

custom_components/powercalc/strategy/fixed.py METASEP
from __future__ import annotations

from decimal import Decimal
from typing import Optional, Union

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components import climate, vacuum
from homeassistant.core import State
from homeassistant.helpers.event import TrackTemplate
from homeassistant.helpers.template import Template

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import CONF_POWER, CONF_STATES_POWER
from custom_components.powercalc.errors import StrategyConfigurationError
from custom_components.powercalc.helpers import evaluate_power

from .strategy_interface import PowerCalculationStrategyInterface

CONFIG_SCHEMA = vol.Schema(
    {
        vol.Optional(CONF_POWER): vol.Any(vol.Coerce(float), cv.template),
        vol.Optional(CONF_STATES_POWER): vol.Schema(
            {cv.string: vol.Any(vol.Coerce(float), cv.template)}
        ),
    }
)

STATE_BASED_ENTITY_DOMAINS = [
    climate.DOMAIN,
    vacuum.DOMAIN,
]


class FixedStrategy(PowerCalculationStrategyInterface):
    def __init__(
        self,
        power: Optional[Union[Template, float]],
        per_state_power: Optional[dict[str, float]],
    ) -> None:
        self._power = power
        self._per_state_power = per_state_power

    async def calculate(self, entity_state: State) -> Optional[Decimal]:
        if self._per_state_power is not None:
            # Lookup by state
            if entity_state.state in self._per_state_power:
                return await evaluate_power(
                    self._per_state_power.get(entity_state.state)
                )
            else:
                # Lookup by state attribute (attribute|value)
                for state_key, power in self._per_state_power.items():
                    if "|" in state_key:
                        attribute, value = state_key.split("|", 2)
                        if str(entity_state.attributes.get(attribute)) == value:
                            return await evaluate_power(power)

        if self._power is None:
            return None

        return await evaluate_power(self._power)

    async def validate_config(self, source_entity: SourceEntity):
        """Validate correct setup of the strategy"""

        if (
            source_entity.domain in STATE_BASED_ENTITY_DOMAINS
            and self._per_state_power is None
        ):
            raise StrategyConfigurationError(
                "This entity can only work with 'states_power' not 'power'"
            )

    def get_entities_to_track(self) -> list[str, TrackTemplate]:
        track_templates = []

        if isinstance(self._power, Template):
            track_templates.append(TrackTemplate(self._power, None))

        if self._per_state_power:
            for power in list(self._per_state_power.values()):
                if isinstance(power, Template):
                    track_templates.append(TrackTemplate(power, None))

        return track_templates

custom_components/powercalc/strategy/factory.py METASEP
from typing import Optional

from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import HomeAssistantType

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import (
    CONF_FIXED,
    CONF_LINEAR,
    CONF_POWER,
    CONF_STANDBY_POWER,
    CONF_STATES_POWER,
    CONF_WLED,
    MODE_FIXED,
    MODE_LINEAR,
    MODE_LUT,
    MODE_WLED,
)
from custom_components.powercalc.errors import (
    StrategyConfigurationError,
    UnsupportedMode,
)
from custom_components.powercalc.light_model import LightModel
from custom_components.powercalc.strategy.fixed import FixedStrategy
from custom_components.powercalc.strategy.linear import LinearStrategy
from custom_components.powercalc.strategy.lut import LutRegistry, LutStrategy
from custom_components.powercalc.strategy.strategy_interface import (
    PowerCalculationStrategyInterface,
)
from custom_components.powercalc.strategy.wled import WledStrategy


class PowerCalculatorStrategyFactory:
    def __init__(self, hass: HomeAssistantType) -> None:
        self._hass = hass
        self._lut_registry = LutRegistry()

    def create(
        self,
        config: dict,
        mode: str,
        light_model: Optional[LightModel],
        source_entity: SourceEntity,
    ) -> PowerCalculationStrategyInterface:
        """Create instance of calculation strategy based on configuration"""
        if mode == MODE_LINEAR:
            return self._create_linear(config, light_model, source_entity)

        if mode == MODE_FIXED:
            return self._create_fixed(config, light_model)

        if mode == MODE_LUT:
            return self._create_lut(light_model)

        if mode == MODE_WLED:
            return self._create_wled(config, source_entity)

        raise UnsupportedMode("Invalid calculation mode", mode)

    def _create_linear(
        self, config: dict, light_model: LightModel, source_entity: SourceEntity
    ) -> LinearStrategy:
        """Create the linear strategy"""
        linear_config = config.get(CONF_LINEAR)

        if linear_config is None and light_model is not None:
            linear_config = light_model.linear_mode_config

        return LinearStrategy(
            linear_config, self._hass, source_entity, config.get(CONF_STANDBY_POWER)
        )

    def _create_fixed(self, config: dict, light_model: LightModel) -> FixedStrategy:
        """Create the fixed strategy"""
        fixed_config = config.get(CONF_FIXED)
        if fixed_config is None and light_model is not None:
            fixed_config = light_model.fixed_mode_config

        power = fixed_config.get(CONF_POWER)
        if isinstance(power, Template):
            power.hass = self._hass

        states_power = fixed_config.get(CONF_STATES_POWER)
        if states_power:
            for p in states_power.values():
                if isinstance(p, Template):
                    p.hass = self._hass

        return FixedStrategy(power, states_power)

    def _create_lut(self, light_model: LightModel) -> LutStrategy:
        """Create the lut strategy"""
        if light_model is None:
            raise StrategyConfigurationError(
                "You must supply a valid manufacturer and model to use the LUT mode"
            )

        return LutStrategy(self._lut_registry, light_model)

    def _create_wled(self, config: dict, source_entity: SourceEntity) -> WledStrategy:
        """Create the WLED strategy"""
        return WledStrategy(
            config=config.get(CONF_WLED),
            light_entity=source_entity,
            hass=self._hass,
            standby_power=config.get(CONF_STANDBY_POWER),
        )

custom_components/powercalc/strategy/__init__.py METASEP

custom_components/powercalc/sensors/utility_meter.py METASEP
from __future__ import annotations

import inspect
import logging
from typing import cast

from awesomeversion.awesomeversion import AwesomeVersion
from homeassistant.const import __version__ as HA_VERSION

if AwesomeVersion(HA_VERSION) >= AwesomeVersion("2022.4.0.dev0"):
    from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
    from homeassistant.components.utility_meter.select import TariffSelect
else:
    from homeassistant.components.utility_meter import TariffSelect

from homeassistant.components.utility_meter.const import (
    DATA_TARIFF_SENSORS,
    DATA_UTILITY,
)
from homeassistant.components.utility_meter.const import DOMAIN as UTILITY_DOMAIN
from homeassistant.components.utility_meter.sensor import UtilityMeterSensor
from homeassistant.const import __short_version__
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import HomeAssistantType

from custom_components.powercalc.const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_ENERGY_SENSOR_PRECISION,
    CONF_UTILITY_METER_OFFSET,
    CONF_UTILITY_METER_TARIFFS,
    CONF_UTILITY_METER_TYPES,
    DEFAULT_ENERGY_SENSOR_PRECISION,
)
from custom_components.powercalc.errors import SensorConfigurationError
from custom_components.powercalc.migrate import async_set_unique_id
from custom_components.powercalc.sensors.energy import EnergySensor

_LOGGER = logging.getLogger(__name__)


async def create_utility_meters(
    hass: HomeAssistantType,
    energy_sensor: EnergySensor,
    sensor_config: dict,
) -> list[UtilityMeterSensor]:
    """Create the utility meters"""
    utility_meters = []

    if not sensor_config.get(CONF_CREATE_UTILITY_METERS):
        return []

    if not DATA_UTILITY in hass.data:
        hass.data[DATA_UTILITY] = {}

    tariffs = sensor_config.get(CONF_UTILITY_METER_TARIFFS)
    meter_types = sensor_config.get(CONF_UTILITY_METER_TYPES)
    for meter_type in meter_types:
        tariff_sensors = []

        name = f"{energy_sensor.name} {meter_type}"
        entity_id = f"{energy_sensor.entity_id}_{meter_type}"
        unique_id = None
        if energy_sensor.unique_id:
            unique_id = f"{energy_sensor.unique_id}_{meter_type}"

        if tariffs:
            tariff_select = await create_tariff_select(tariffs, hass, name, unique_id)

            for tariff in tariffs:
                utility_meter = await create_utility_meter(
                    hass,
                    energy_sensor.entity_id,
                    entity_id,
                    name,
                    sensor_config,
                    meter_type,
                    unique_id,
                    tariff,
                    tariff_select.entity_id,
                )
                tariff_sensors.append(utility_meter)
                utility_meters.append(utility_meter)

        else:
            utility_meter = await create_utility_meter(
                hass,
                energy_sensor.entity_id,
                entity_id,
                name,
                sensor_config,
                meter_type,
                unique_id,
            )
            tariff_sensors.append(utility_meter)
            utility_meters.append(utility_meter)

        hass.data[DATA_UTILITY][entity_id] = {DATA_TARIFF_SENSORS: tariff_sensors}

    return utility_meters


async def create_tariff_select(
    tariffs: list, hass: HomeAssistantType, name: str, unique_id: str | None
):
    """Create tariff selection entity"""

    _LOGGER.debug(f"Creating utility_meter tariff select: {name}")
    utility_meter_component = cast(
        EntityComponent, hass.data["entity_components"].get(UTILITY_DOMAIN)
    )
    if utility_meter_component is None:
        utility_meter_component = (
            hass.data.get("utility_meter_legacy_component") or None
        )

    if utility_meter_component is None:
        raise SensorConfigurationError("Cannot find utility_meter component")

    if AwesomeVersion(HA_VERSION) >= AwesomeVersion("2022.4.0.dev0"):
        select_component = cast(EntityComponent, hass.data[SELECT_DOMAIN])
        if AwesomeVersion(HA_VERSION) >= AwesomeVersion("2022.4.0"):
            select_unique_id = None
            if unique_id:
                select_unique_id = f"{unique_id}_select"
            tariff_select = TariffSelect(
                name,
                list(tariffs),
                utility_meter_component.async_add_entities,
                select_unique_id,
            )
        else:
            tariff_select = TariffSelect(
                name, list(tariffs), utility_meter_component.async_add_entities
            )
        await select_component.async_add_entities([tariff_select])
    else:
        tariff_select = TariffSelect(name, list(tariffs))
        await utility_meter_component.async_add_entities([tariff_select])

    return tariff_select


async def create_utility_meter(
    hass: HomeAssistantType,
    source_entity: str,
    entity_id: str,
    name: str,
    sensor_config: dict,
    meter_type: str,
    unique_id: str = None,
    tariff: str = None,
    tariff_entity: str = None,
) -> VirtualUtilityMeter:
    """Create a utility meter entity, one per tariff"""

    parent_meter = entity_id
    if tariff:
        name = f"{name} {tariff}"
        entity_id = f"{entity_id}_{tariff}"
        if unique_id:
            unique_id = f"{unique_id}_{tariff}"

    _LOGGER.debug(f"Creating utility_meter sensor: {name} (entity_id={entity_id})")

    params = {
        "source_entity": source_entity,
        "name": name,
        "meter_type": meter_type,
        "meter_offset": sensor_config.get(CONF_UTILITY_METER_OFFSET),
        "net_consumption": False,
        "tariff": tariff,
        "tariff_entity": tariff_entity,
    }

    signature = inspect.signature(UtilityMeterSensor.__init__)
    if "parent_meter" in signature.parameters:
        params["parent_meter"] = parent_meter
    if "delta_values" in signature.parameters:
        params["delta_values"] = False
    if "unique_id" in signature.parameters:
        params["unique_id"] = unique_id
    if "cron_pattern" in signature.parameters:
        params["cron_pattern"] = None

    utility_meter = VirtualUtilityMeter(**params)
    setattr(
        utility_meter,
        "rounding_digits",
        sensor_config.get(CONF_ENERGY_SENSOR_PRECISION),
    )

    # This is for BC purposes, for HA versions lower than 2022.4. May be removed in the future
    if not "unique_id" in params and unique_id:
        # Set new unique id if this entity already exists in the entity registry
        async_set_unique_id(hass, entity_id, unique_id)
        utility_meter.unique_id = unique_id

    utility_meter.entity_id = entity_id

    return utility_meter


class VirtualUtilityMeter(UtilityMeterSensor):
    rounding_digits: int = DEFAULT_ENERGY_SENSOR_PRECISION

    @property
    def unique_id(self):
        """Return the unique id."""
        return self._attr_unique_id

    @unique_id.setter
    def unique_id(self, value):
        """Set unique id."""
        self._attr_unique_id = value

    @property
    def native_value(self):
        """Return the state of the sensor."""
        if self.rounding_digits and self._state is not None:
            return round(self._state, self.rounding_digits)

        return self._state

custom_components/powercalc/sensors/power.py METASEP
from __future__ import annotations

import logging
from decimal import Decimal
from typing import Optional

import homeassistant.helpers.entity_registry as er
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorStateClass,
)
from homeassistant.const import (
    CONF_NAME,
    CONF_SCAN_INTERVAL,
    CONF_UNIQUE_ID,
    EVENT_HOMEASSISTANT_START,
    POWER_WATT,
    STATE_ON,
    STATE_UNAVAILABLE,
    STATE_UNKNOWN,
)
from homeassistant.core import State, callback
from homeassistant.helpers.entity import EntityCategory, async_generate_entity_id
from homeassistant.helpers.event import (
    TrackTemplate,
    async_track_state_change_event,
    async_track_template_result,
    async_track_time_interval,
)
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import DiscoveryInfoType, HomeAssistantType

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import (
    ATTR_CALCULATION_MODE,
    ATTR_INTEGRATION,
    ATTR_SOURCE_DOMAIN,
    ATTR_SOURCE_ENTITY,
    CONF_CALCULATION_ENABLED_CONDITION,
    CONF_DISABLE_STANDBY_POWER,
    CONF_FIXED,
    CONF_IGNORE_UNAVAILABLE_STATE,
    CONF_LINEAR,
    CONF_MODE,
    CONF_MODEL,
    CONF_MULTIPLY_FACTOR,
    CONF_MULTIPLY_FACTOR_STANDBY,
    CONF_POWER_SENSOR_CATEGORY,
    CONF_POWER_SENSOR_FRIENDLY_NAMING,
    CONF_POWER_SENSOR_ID,
    CONF_POWER_SENSOR_NAMING,
    CONF_POWER_SENSOR_PRECISION,
    CONF_STANDBY_POWER,
    CONF_WLED,
    DATA_CALCULATOR_FACTORY,
    DISCOVERY_LIGHT_MODEL,
    DOMAIN,
    DUMMY_ENTITY_ID,
    MODE_FIXED,
    MODE_LINEAR,
    MODE_WLED,
    OFF_STATES,
)
from custom_components.powercalc.errors import (
    ModelNotSupported,
    StrategyConfigurationError,
    UnsupportedMode,
)
from custom_components.powercalc.migrate import async_migrate_entity_id
from custom_components.powercalc.model_discovery import get_light_model
from custom_components.powercalc.strategy.factory import PowerCalculatorStrategyFactory
from custom_components.powercalc.strategy.strategy_interface import (
    PowerCalculationStrategyInterface,
)

ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}"

_LOGGER = logging.getLogger(__name__)


async def create_power_sensor(
    hass: HomeAssistantType,
    sensor_config: dict,
    source_entity: SourceEntity,
    discovery_info: DiscoveryInfoType | None = None,
) -> PowerSensor:
    """Create the power sensor based on powercalc sensor configuration"""

    if CONF_POWER_SENSOR_ID in sensor_config:
        # Use an existing power sensor, only create energy sensors / utility meters
        return await create_real_power_sensor(hass, sensor_config)

    return await create_virtual_power_sensor(
        hass, sensor_config, source_entity, discovery_info
    )


async def create_virtual_power_sensor(
    hass: HomeAssistantType,
    sensor_config: dict,
    source_entity: SourceEntity,
    discovery_info: DiscoveryInfoType | None = None,
) -> VirtualPowerSensor:
    """Create the power sensor entity"""

    name_pattern = sensor_config.get(CONF_POWER_SENSOR_NAMING)
    name = sensor_config.get(CONF_NAME) or source_entity.name
    if CONF_POWER_SENSOR_FRIENDLY_NAMING in sensor_config:
        friendly_name_pattern = sensor_config.get(CONF_POWER_SENSOR_FRIENDLY_NAMING)
        name = friendly_name_pattern.format(name)
    else:
        name = name_pattern.format(name)

    object_id = sensor_config.get(CONF_NAME) or source_entity.object_id
    entity_category = sensor_config.get(CONF_POWER_SENSOR_CATEGORY)
    entity_id = async_generate_entity_id(
        ENTITY_ID_FORMAT, name_pattern.format(object_id), hass=hass
    )

    unique_id = sensor_config.get(CONF_UNIQUE_ID) or source_entity.unique_id
    if unique_id:
        async_migrate_entity_id(hass, SENSOR_DOMAIN, unique_id, entity_id)

    light_model = None
    try:
        mode = select_calculation_mode(sensor_config)

        # When the user did not manually configured a model and a model was auto discovered we can load it.
        try:
            if (
                discovery_info
                and sensor_config.get(CONF_MODEL) is None
                and discovery_info.get(DISCOVERY_LIGHT_MODEL)
            ):
                light_model = discovery_info.get(DISCOVERY_LIGHT_MODEL)
            else:
                light_model = await get_light_model(
                    hass, sensor_config, source_entity.entity_entry
                )
            if mode is None and light_model:
                mode = light_model.supported_modes[0]
        except (ModelNotSupported) as err:
            if not is_fully_configured(sensor_config):
                _LOGGER.error(
                    "Skipping sensor setup %s: %s", source_entity.entity_id, err
                )
                raise err

        if mode is None:
            raise UnsupportedMode(
                "Cannot select a mode (LINEAR, FIXED or LUT, WLED), supply it in the config"
            )

        calculation_strategy_factory: PowerCalculatorStrategyFactory = hass.data[
            DOMAIN
        ][DATA_CALCULATOR_FACTORY]
        calculation_strategy = calculation_strategy_factory.create(
            sensor_config, mode, light_model, source_entity
        )
        await calculation_strategy.validate_config(source_entity)
    except (UnsupportedMode) as err:
        _LOGGER.error("Skipping sensor setup %s: %s", source_entity.entity_id, err)
        raise err
    except StrategyConfigurationError as err:
        _LOGGER.error(
            "%s: Error setting up calculation strategy: %s",
            source_entity.entity_id,
            err,
        )
        raise err

    standby_power = Decimal(0)
    standby_power_on = Decimal(0)
    if not sensor_config.get(CONF_DISABLE_STANDBY_POWER):
        if CONF_STANDBY_POWER in sensor_config:
            standby_power = Decimal(sensor_config.get(CONF_STANDBY_POWER))
        elif light_model is not None:
            standby_power = Decimal(light_model.standby_power)
            standby_power_on = Decimal(light_model.standby_power_on)

    if (
        not CONF_CALCULATION_ENABLED_CONDITION in sensor_config
        and light_model is not None
        and light_model.calculation_enabled_condition
    ):
        sensor_config[
            CONF_CALCULATION_ENABLED_CONDITION
        ] = light_model.calculation_enabled_condition

    _LOGGER.debug(
        "Creating power sensor (entity_id=%s entity_category=%s, sensor_name=%s strategy=%s manufacturer=%s model=%s standby_power=%s unique_id=%s)",
        source_entity.entity_id,
        entity_category,
        name,
        calculation_strategy.__class__.__name__,
        light_model.manufacturer if light_model else "",
        light_model.model if light_model else "",
        standby_power,
        unique_id,
    )

    return VirtualPowerSensor(
        power_calculator=calculation_strategy,
        calculation_mode=mode,
        entity_id=entity_id,
        entity_category=entity_category,
        name=name,
        source_entity=source_entity.entity_id,
        source_domain=source_entity.domain,
        unique_id=unique_id,
        standby_power=standby_power,
        standby_power_on=standby_power_on,
        scan_interval=sensor_config.get(CONF_SCAN_INTERVAL),
        multiply_factor=sensor_config.get(CONF_MULTIPLY_FACTOR),
        multiply_factor_standby=sensor_config.get(CONF_MULTIPLY_FACTOR_STANDBY),
        ignore_unavailable_state=sensor_config.get(CONF_IGNORE_UNAVAILABLE_STATE),
        rounding_digits=sensor_config.get(CONF_POWER_SENSOR_PRECISION),
        sensor_config=sensor_config,
    )


async def create_real_power_sensor(
    hass: HomeAssistantType, sensor_config: dict
) -> RealPowerSensor:
    """Create reference to an existing power sensor"""

    power_sensor_id = sensor_config.get(CONF_POWER_SENSOR_ID)
    unique_id = sensor_config.get(CONF_UNIQUE_ID)
    device_id = None
    ent_reg = er.async_get(hass)
    entity_entry = ent_reg.async_get(power_sensor_id)
    if entity_entry:
        if not unique_id:
            unique_id = entity_entry.unique_id
        device_id = entity_entry.device_id

    return RealPowerSensor(
        entity_id=power_sensor_id, device_id=device_id, unique_id=unique_id
    )


def select_calculation_mode(config: dict) -> Optional[str]:
    """Select the calculation mode"""
    config_mode = config.get(CONF_MODE)
    if config_mode:
        return config_mode

    if config.get(CONF_LINEAR):
        return MODE_LINEAR

    if config.get(CONF_FIXED):
        return MODE_FIXED

    if config.get(CONF_WLED):
        return MODE_WLED

    return None


def is_fully_configured(config) -> bool:
    if config.get(CONF_FIXED):
        return True
    if config.get(CONF_LINEAR):
        return True
    if config.get(CONF_WLED):
        return True
    return False


class PowerSensor:
    """Class which all power sensors should extend from"""

    pass


class VirtualPowerSensor(SensorEntity, PowerSensor):
    """Virtual power sensor"""

    _attr_device_class = SensorDeviceClass.POWER
    _attr_state_class = SensorStateClass.MEASUREMENT
    _attr_native_unit_of_measurement = POWER_WATT

    def __init__(
        self,
        power_calculator: PowerCalculationStrategyInterface,
        calculation_mode: str,
        entity_id: str,
        entity_category: str,
        name: str,
        source_entity: str,
        source_domain: str,
        unique_id: str,
        standby_power: Decimal,
        standby_power_on: Decimal,
        scan_interval,
        multiply_factor: float | None,
        multiply_factor_standby: bool,
        ignore_unavailable_state: bool,
        rounding_digits: int,
        sensor_config: dict,
    ):
        """Initialize the sensor."""
        self._power_calculator = power_calculator
        self._calculation_mode = calculation_mode
        self._source_entity = source_entity
        self._source_domain = source_domain
        self._name = name
        self._power = None
        self._standby_power = standby_power
        self._standby_power_on = standby_power_on
        self._attr_force_update = True
        self._attr_unique_id = unique_id
        self._scan_interval = scan_interval
        self._multiply_factor = multiply_factor
        self._multiply_factor_standby = multiply_factor_standby
        self._ignore_unavailable_state = ignore_unavailable_state
        self._rounding_digits = rounding_digits
        self.entity_id = entity_id
        self._sensor_config = sensor_config
        if entity_category:
            self._attr_entity_category = EntityCategory(entity_category)

    async def async_added_to_hass(self):
        """Register callbacks."""

        async def appliance_state_listener(event):
            """Handle for state changes for dependent sensors."""
            new_state = event.data.get("new_state")

            await self._update_power_sensor(self._source_entity, new_state)

        async def template_change_listener(*args):
            state = self.hass.states.get(self._source_entity)
            await self._update_power_sensor(self._source_entity, state)

        async def home_assistant_startup(event):
            """Add listeners and get initial state."""
            entities_to_track = self._power_calculator.get_entities_to_track()

            track_entities = [
                entity for entity in entities_to_track if isinstance(entity, str)
            ]
            if not track_entities:
                track_entities = [self._source_entity]

            async_track_state_change_event(
                self.hass, track_entities, appliance_state_listener
            )

            track_templates = [
                template
                for template in entities_to_track
                if isinstance(template, TrackTemplate)
            ]
            if track_templates:
                async_track_template_result(
                    self.hass,
                    track_templates=track_templates,
                    action=template_change_listener,
                )

            for entity_id in track_entities:
                if entity_id == DUMMY_ENTITY_ID:
                    new_state = State(entity_id, STATE_ON)
                else:
                    new_state = self.hass.states.get(entity_id)

                await self._update_power_sensor(entity_id, new_state)

        @callback
        def async_update(event_time=None):
            """Update the entity."""
            self.async_schedule_update_ha_state(True)

        async_track_time_interval(self.hass, async_update, self._scan_interval)

        self.hass.bus.async_listen_once(
            EVENT_HOMEASSISTANT_START, home_assistant_startup
        )

    async def _update_power_sensor(self, trigger_entity_id: str, state: State) -> bool:
        """Update power sensor based on new dependant entity state."""
        if (
            state is None
            or state.state == STATE_UNKNOWN
            or (not self._ignore_unavailable_state and state.state == STATE_UNAVAILABLE)
        ):
            _LOGGER.debug(
                "%s: Source entity has an invalid state, setting power sensor to unavailable",
                trigger_entity_id,
            )
            self._power = None
            self.async_write_ha_state()
            return False

        self._power = await self.calculate_power(state)

        if self._power is not None:
            self._power = round(self._power, self._rounding_digits)

        _LOGGER.debug(
            '%s: State changed to "%s". Power:%s',
            state.entity_id,
            state.state,
            self._power,
        )

        if self._power is None:
            self.async_write_ha_state()
            return False

        self.async_write_ha_state()
        return True

    async def calculate_power(self, state: State) -> Optional[Decimal]:
        """Calculate power consumption using configured strategy."""

        is_calculation_enabled = await self.is_calculation_enabled()
        if state.state in OFF_STATES or not is_calculation_enabled:
            standby_power = self._standby_power
            if self._power_calculator.can_calculate_standby():
                standby_power = await self._power_calculator.calculate(state)

            if self._multiply_factor_standby and self._multiply_factor:
                standby_power *= Decimal(self._multiply_factor)
            return Decimal(standby_power)

        power = await self._power_calculator.calculate(state)
        if power is None:
            return None

        if self._multiply_factor:
            power *= Decimal(self._multiply_factor)

        if self._standby_power_on:
            standby_power = self._standby_power_on
            if self._multiply_factor_standby and self._multiply_factor:
                standby_power *= Decimal(self._multiply_factor)
            power += standby_power

        return Decimal(power)

    async def is_calculation_enabled(self) -> bool:
        if not CONF_CALCULATION_ENABLED_CONDITION in self._sensor_config:
            return True

        template = self._sensor_config.get(CONF_CALCULATION_ENABLED_CONDITION)
        if isinstance(template, str):
            template = template.replace("[[entity]]", self.source_entity)
            template = Template(template)

        template.hass = self.hass
        return bool(template.async_render())

    @property
    def source_entity(self):
        """The source entity this power sensor calculates power for."""
        return self._source_entity

    @property
    def extra_state_attributes(self):
        """Return entity state attributes."""
        return {
            ATTR_CALCULATION_MODE: self._calculation_mode,
            ATTR_INTEGRATION: DOMAIN,
            ATTR_SOURCE_ENTITY: self._source_entity,
            ATTR_SOURCE_DOMAIN: self._source_domain,
        }

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._power

    @property
    def available(self):
        """Return True if entity is available."""
        return self._power is not None


class RealPowerSensor(PowerSensor):
    """Contains a reference to a existing real power sensor entity"""

    def __init__(self, entity_id: str, device_id: str = None, unique_id: str = None):
        self._entity_id = entity_id
        self._device_id = device_id
        self._unique_id = unique_id

    @property
    def entity_id(self) -> str:
        """Return the name of the sensor."""
        return self._entity_id

    @property
    def device_id(self) -> str:
        """Return the device_id of the sensor."""
        return self._device_id

    @property
    def unique_id(self) -> str:
        """Return the unique_id of the sensor."""
        return self._unique_id

custom_components/powercalc/sensors/group.py METASEP
from __future__ import annotations

import logging
from decimal import Decimal
from typing import Callable

from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorStateClass,
)
from homeassistant.const import (
    CONF_UNIQUE_ID,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
    STATE_UNAVAILABLE,
    STATE_UNKNOWN,
)
from homeassistant.core import State, callback
from homeassistant.helpers import entity_registry
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import HomeAssistantType

from custom_components.powercalc.const import (
    ATTR_ENTITIES,
    ATTR_IS_GROUP,
    CONF_ENERGY_SENSOR_NAMING,
    CONF_ENERGY_SENSOR_PRECISION,
    CONF_POWER_SENSOR_NAMING,
    CONF_POWER_SENSOR_PRECISION,
)
from custom_components.powercalc.sensors.energy import EnergySensor, RealEnergySensor
from custom_components.powercalc.sensors.power import PowerSensor, RealPowerSensor
from custom_components.powercalc.sensors.utility_meter import create_utility_meters

ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}"

_LOGGER = logging.getLogger(__name__)


async def create_group_sensors(
    group_name: str,
    sensor_config: dict,
    entities: list[SensorEntity, RealPowerSensor, RealEnergySensor],
    hass: HomeAssistantType,
    filters: list[Callable, None] = [],
) -> list[GroupedSensor]:
    """Create grouped power and energy sensors."""

    def _get_filtered_entity_ids_by_class(
        all_entities: list, default_filters: list[Callable], className
    ) -> list[str]:
        filters = default_filters.copy()
        filters.append(lambda elm: not isinstance(elm, GroupedSensor))
        filters.append(lambda elm: isinstance(elm, className))
        return list(
            map(
                lambda x: x.entity_id,
                list(
                    filter(
                        lambda x: all(f(x) for f in filters),
                        all_entities,
                    )
                ),
            )
        )

    group_sensors = []

    power_sensor_ids = _get_filtered_entity_ids_by_class(entities, filters, PowerSensor)
    name_pattern = sensor_config.get(CONF_POWER_SENSOR_NAMING)
    name = name_pattern.format(group_name)
    unique_id = sensor_config.get(CONF_UNIQUE_ID)
    entity_id = await create_entity_id(hass, name, unique_id)
    group_sensors.append(
        GroupedPowerSensor(
            name=name,
            entities=power_sensor_ids,
            unique_id=unique_id,
            rounding_digits=sensor_config.get(CONF_POWER_SENSOR_PRECISION),
            entity_id=entity_id,
        )
    )
    _LOGGER.debug(f"Creating grouped power sensor: %s", name)

    energy_sensor_ids = _get_filtered_entity_ids_by_class(
        entities, filters, EnergySensor
    )
    name_pattern = sensor_config.get(CONF_ENERGY_SENSOR_NAMING)
    name = name_pattern.format(group_name)
    energy_unique_id = None
    if unique_id:
        energy_unique_id = f"{unique_id}_energy"
    entity_id = await create_entity_id(hass, name, energy_unique_id)
    group_energy_sensor = GroupedEnergySensor(
        name=name,
        entities=energy_sensor_ids,
        unique_id=energy_unique_id,
        rounding_digits=sensor_config.get(CONF_ENERGY_SENSOR_PRECISION),
        entity_id=entity_id,
    )
    group_sensors.append(group_energy_sensor)
    _LOGGER.debug("Creating grouped energy sensor: %s", name)

    group_sensors.extend(
        await create_utility_meters(hass, group_energy_sensor, sensor_config)
    )

    return group_sensors


async def create_entity_id(hass: HomeAssistantType, name: str, unique_id: str | None):
    """
    Check if we already have an entity id based on the unique id of the group sensor
    When this is not the case we generate one using same algorithm as HA add entity routine
    """
    if unique_id is not None:
        ent_reg = entity_registry.async_get(hass)
        if entity_id := ent_reg.async_get_entity_id(
            SENSOR_DOMAIN, SENSOR_DOMAIN, unique_id
        ):
            return entity_id

    return async_generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass)


class GroupedSensor(RestoreEntity, SensorEntity):
    """Base class for grouped sensors"""

    _attr_should_poll = False

    def __init__(
        self,
        name: str,
        entities: list[str],
        entity_id: str,
        unique_id: str = None,
        rounding_digits: int = 2,
    ):
        self._attr_name = name
        self._entities = entities
        self._attr_extra_state_attributes = {
            ATTR_ENTITIES: self._entities,
            ATTR_IS_GROUP: True,
        }
        self._rounding_digits = rounding_digits
        if unique_id:
            self._attr_unique_id = unique_id
        self.entity_id = entity_id

    async def async_added_to_hass(self) -> None:
        """Register state listeners."""
        await super().async_added_to_hass()

        if (state := await self.async_get_last_state()) is not None:
            self._attr_native_value = state.state

        async_track_state_change_event(self.hass, self._entities, self.on_state_change)

    @callback
    def on_state_change(self, event):
        """Triggered when one of the group entities changes state"""
        all_states = [self.hass.states.get(entity_id) for entity_id in self._entities]
        states: list[State] = list(filter(None, all_states))
        ignored_states = (STATE_UNAVAILABLE, STATE_UNKNOWN)
        summed = sum(
            Decimal(state.state)
            for state in states
            if state.state not in ignored_states
        )

        self._attr_native_value = round(summed, self._rounding_digits)
        self.async_schedule_update_ha_state(True)


class GroupedPowerSensor(GroupedSensor, PowerSensor):
    """Grouped power sensor. Sums all values of underlying individual power sensors"""

    _attr_device_class = SensorDeviceClass.POWER
    _attr_state_class = SensorStateClass.MEASUREMENT
    _attr_native_unit_of_measurement = POWER_WATT


class GroupedEnergySensor(GroupedSensor, EnergySensor):
    """Grouped energy sensor. Sums all values of underlying individual energy sensors"""

    _attr_device_class = SensorDeviceClass.ENERGY
    _attr_state_class = SensorStateClass.TOTAL
    _attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR

custom_components/powercalc/sensors/energy.py METASEP
from __future__ import annotations

import logging
from typing import Any, Optional

import homeassistant.helpers.entity_registry as er
from awesomeversion.awesomeversion import AwesomeVersion
from homeassistant.components.integration.sensor import IntegrationSensor
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import CONF_NAME, ENERGY_KILO_WATT_HOUR, TIME_HOURS
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.core import callback
from homeassistant.helpers.entity import EntityCategory, async_generate_entity_id
from homeassistant.helpers.typing import HomeAssistantType

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import (
    ATTR_SOURCE_DOMAIN,
    ATTR_SOURCE_ENTITY,
    CONF_ENERGY_INTEGRATION_METHOD,
    CONF_ENERGY_SENSOR_CATEGORY,
    CONF_ENERGY_SENSOR_FRIENDLY_NAMING,
    CONF_ENERGY_SENSOR_ID,
    CONF_ENERGY_SENSOR_NAMING,
    CONF_ENERGY_SENSOR_PRECISION,
    CONF_POWER_SENSOR_ID,
    DEFAULT_ENERGY_INTEGRATION_METHOD,
)
from custom_components.powercalc.migrate import async_migrate_entity_id
from custom_components.powercalc.sensors.power import PowerSensor, RealPowerSensor

ENERGY_ICON = "mdi:lightning-bolt"
ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}"

_LOGGER = logging.getLogger(__name__)


async def create_energy_sensor(
    hass: HomeAssistantType,
    sensor_config: dict,
    power_sensor: PowerSensor,
    source_entity: SourceEntity,
) -> EnergySensor:
    """Create the energy sensor entity"""

    # User specified an existing energy sensor with "energy_sensor_id" option. Just return that one
    if CONF_ENERGY_SENSOR_ID in sensor_config:
        ent_reg = er.async_get(hass)
        entity_entry = ent_reg.async_get(sensor_config[CONF_ENERGY_SENSOR_ID])
        return RealEnergySensor(entity_entry)

    # User specified an existing power sensor with "power_sensor_id" option. Try to find a corresponding energy sensor
    if CONF_POWER_SENSOR_ID in sensor_config and isinstance(
        power_sensor, RealPowerSensor
    ):
        real_energy_sensor = find_related_real_energy_sensor(hass, power_sensor)
        if real_energy_sensor:
            _LOGGER.debug(
                f"Found existing energy sensor '{real_energy_sensor.entity_id}' for the power sensor '{power_sensor.entity_id}'"
            )
            return real_energy_sensor

        _LOGGER.debug(
            f"No existing energy sensor found for the power sensor '{power_sensor.entity_id}'"
        )

    # Create an energy sensor based on riemann integral integration, which uses the virtual powercalc sensor as source.
    name_pattern = sensor_config.get(CONF_ENERGY_SENSOR_NAMING)
    name = sensor_config.get(CONF_NAME) or source_entity.name
    if CONF_ENERGY_SENSOR_FRIENDLY_NAMING in sensor_config:
        friendly_name_pattern = sensor_config.get(CONF_ENERGY_SENSOR_FRIENDLY_NAMING)
        name = friendly_name_pattern.format(name)
    else:
        name = name_pattern.format(name)
    object_id = sensor_config.get(CONF_NAME) or source_entity.object_id
    entity_id = async_generate_entity_id(
        ENTITY_ID_FORMAT, name_pattern.format(object_id), hass=hass
    )
    entity_category = sensor_config.get(CONF_ENERGY_SENSOR_CATEGORY)
    unique_id = None
    if power_sensor.unique_id:
        unique_id = f"{power_sensor.unique_id}_energy"
        async_migrate_entity_id(hass, SENSOR_DOMAIN, unique_id, entity_id)

    _LOGGER.debug("Creating energy sensor: %s", name)
    return VirtualEnergySensor(
        source_entity=power_sensor.entity_id,
        unique_id=unique_id,
        entity_id=entity_id,
        entity_category=entity_category,
        name=name,
        round_digits=sensor_config.get(CONF_ENERGY_SENSOR_PRECISION),
        unit_prefix="k",
        unit_of_measurement=None,
        unit_time=TIME_HOURS,
        integration_method=sensor_config.get(CONF_ENERGY_INTEGRATION_METHOD)
        or DEFAULT_ENERGY_INTEGRATION_METHOD,
        powercalc_source_entity=source_entity.entity_id,
        powercalc_source_domain=source_entity.domain,
    )


@callback
def find_related_real_energy_sensor(
    hass: HomeAssistantType, power_sensor: RealPowerSensor
) -> Optional[RealEnergySensor]:
    """See if a corresponding energy sensor exists in the HA installation for the power sensor"""

    if not power_sensor.device_id:
        return None

    ent_reg = er.async_get(hass)
    energy_sensors = [
        entry
        for entry in er.async_entries_for_device(
            ent_reg, device_id=power_sensor.device_id
        )
        if entry.device_class == SensorDeviceClass.ENERGY
        or entry.unit_of_measurement == ENERGY_KILO_WATT_HOUR
    ]
    if not energy_sensors:
        return None

    return RealEnergySensor(energy_sensors[0])


class EnergySensor:
    """Class which all power sensors should extend from"""

    pass


class VirtualEnergySensor(IntegrationSensor, EnergySensor):
    """Virtual energy sensor, totalling kWh"""

    def __init__(
        self,
        source_entity,
        unique_id,
        entity_id,
        entity_category,
        name,
        round_digits,
        unit_prefix,
        unit_time,
        unit_of_measurement,
        integration_method,
        powercalc_source_entity: str,
        powercalc_source_domain: str,
    ):
        if AwesomeVersion(HA_VERSION) >= AwesomeVersion("2022.5.0.dev0"):
            super().__init__(
                source_entity=source_entity,
                name=name,
                round_digits=round_digits,
                unit_prefix=unit_prefix,
                unit_time=unit_time,
                integration_method=integration_method,
                unique_id=unique_id,
            )
        elif AwesomeVersion(HA_VERSION) >= AwesomeVersion("2022.4.0.dev0"):
            super().__init__(
                source_entity=source_entity,
                name=name,
                round_digits=round_digits,
                unit_prefix=unit_prefix,
                unit_time=unit_time,
                unit_of_measurement=unit_of_measurement,
                integration_method=integration_method,
                unique_id=unique_id,
            )
        else:
            super().__init__(
                source_entity=source_entity,
                name=name,
                round_digits=round_digits,
                unit_prefix=unit_prefix,
                unit_time=unit_time,
                unit_of_measurement=unit_of_measurement,
                integration_method=integration_method,
            )
            if unique_id:
                self._attr_unique_id = unique_id

        self._powercalc_source_entity = powercalc_source_entity
        self._powercalc_source_domain = powercalc_source_domain
        self.entity_id = entity_id
        if entity_category:
            self._attr_entity_category = EntityCategory(entity_category)

    @property
    def extra_state_attributes(self) -> dict[str, Any]:
        """Return the state attributes of the acceleration sensor."""
        state_attr = super().extra_state_attributes
        state_attr[ATTR_SOURCE_ENTITY] = self._powercalc_source_entity
        state_attr[ATTR_SOURCE_DOMAIN] = self._powercalc_source_domain
        return state_attr

    @property
    def icon(self):
        return ENERGY_ICON

    @callback
    def async_reset_energy(self) -> None:
        _LOGGER.debug(f"{self.entity_id}: Reset energy sensor")
        self._state = 0
        self.async_write_ha_state()


class RealEnergySensor(EnergySensor):
    """Contains a reference to a existing energy sensor entity"""

    def __init__(self, entity_entry: er.RegistryEntry):
        self._entity_entry = entity_entry

    @property
    def entity_id(self) -> str:
        """Return the entity_id of the sensor."""
        return self._entity_entry.entity_id

    @property
    def name(self) -> str:
        """Return the name of the sensor."""
        return self._entity_entry.name or self._entity_entry.original_name

    @property
    def unique_id(self) -> str | None:
        """Return the unique_id of the sensor."""
        return self._entity_entry.unique_id

custom_components/powercalc/sensors/daily_energy.py METASEP
from __future__ import annotations

import logging
from datetime import datetime, timedelta
from decimal import Decimal

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorStateClass,
)
from homeassistant.const import (
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.core import callback
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import HomeAssistantType

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import (
    CONF_DAILY_FIXED_ENERGY,
    CONF_ENERGY_SENSOR_CATEGORY,
    CONF_ENERGY_SENSOR_PRECISION,
    CONF_FIXED,
    CONF_ON_TIME,
    CONF_POWER,
    CONF_START_TIME,
    CONF_UPDATE_FREQUENCY,
    CONF_VALUE,
)
from custom_components.powercalc.sensors.power import create_virtual_power_sensor

from .energy import EnergySensor
from .power import VirtualPowerSensor

ENERGY_ICON = "mdi:lightning-bolt"
ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}"

DEFAULT_DAILY_UPDATE_FREQUENCY = 1800
DAILY_FIXED_ENERGY_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_VALUE): vol.Any(vol.Coerce(float), cv.template),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME, default=timedelta(days=1)): cv.time_period,
        vol.Optional(CONF_START_TIME): cv.time,
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): vol.Coerce(int),
    }
)

_LOGGER = logging.getLogger(__name__)


async def create_daily_fixed_energy_sensor(
    hass: HomeAssistantType, sensor_config: dict
) -> DailyEnergySensor:
    mode_config: dict = sensor_config.get(CONF_DAILY_FIXED_ENERGY)

    _LOGGER.debug(
        "Creating daily_fixed_energy energy sensor (name=%s unique_id=%s)",
        sensor_config.get(CONF_NAME),
        sensor_config.get(CONF_UNIQUE_ID),
    )

    return DailyEnergySensor(
        hass,
        sensor_config.get(CONF_NAME),
        sensor_config.get(CONF_ENERGY_SENSOR_CATEGORY),
        mode_config.get(CONF_VALUE),
        mode_config.get(CONF_UNIT_OF_MEASUREMENT),
        mode_config.get(CONF_UPDATE_FREQUENCY),
        unique_id=sensor_config.get(CONF_UNIQUE_ID),
        on_time=mode_config.get(CONF_ON_TIME),
        start_time=mode_config.get(CONF_START_TIME),
        rounding_digits=sensor_config.get(CONF_ENERGY_SENSOR_PRECISION),
    )


async def create_daily_fixed_energy_power_sensor(
    hass: HomeAssistantType, sensor_config: dict, source_entity: SourceEntity
) -> VirtualPowerSensor | None:
    mode_config: dict = sensor_config.get(CONF_DAILY_FIXED_ENERGY)
    if mode_config.get(CONF_UNIT_OF_MEASUREMENT) != POWER_WATT:
        return None

    if mode_config.get(CONF_ON_TIME) != timedelta(days=1):
        return None

    power_sensor_config = sensor_config.copy()
    power_sensor_config[CONF_FIXED] = {CONF_POWER: mode_config.get(CONF_VALUE)}

    unique_id = sensor_config.get(CONF_UNIQUE_ID)
    if unique_id:
        power_sensor_config[CONF_UNIQUE_ID] = f"{unique_id}_power"

    _LOGGER.debug(
        "Creating daily_fixed_energy power sensor (name=%s unique_id=%s)",
        sensor_config.get(CONF_NAME),
        unique_id,
    )

    return await create_virtual_power_sensor(hass, power_sensor_config, source_entity)


class DailyEnergySensor(RestoreEntity, SensorEntity, EnergySensor):
    _attr_device_class = SensorDeviceClass.ENERGY
    _attr_state_class = SensorStateClass.TOTAL_INCREASING
    _attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
    _attr_should_poll = False
    _attr_icon = ENERGY_ICON

    def __init__(
        self,
        hass: HomeAssistantType,
        name: str,
        entity_category: str,
        value: float,
        unit_of_measurement: str,
        update_frequency: int,
        unique_id: str = None,
        on_time: timedelta = None,
        start_time=None,
        rounding_digits: int = 4,
    ):
        self._hass = hass
        self._attr_name = name
        self._attr_entity_category = entity_category
        self._value = value
        self._unit_of_measurement = unit_of_measurement
        self._update_frequency = update_frequency
        self._on_time = on_time or timedelta(days=1)
        self._start_time = start_time
        self._rounding_digits = rounding_digits
        self._attr_unique_id = unique_id
        self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass)

    async def async_added_to_hass(self):
        """Handle entity which will be added."""

        if state := await self.async_get_last_state():
            self._state = Decimal(state.state)
            delta = self.calculate_delta(
                round(datetime.now().timestamp() - state.last_changed.timestamp())
            )
            self._state = self._state + delta
            self.async_schedule_update_ha_state()
        else:
            self._state = Decimal(0)

        _LOGGER.debug(f"{self.entity_id}: Restoring state: {self._state}")

        @callback
        def refresh(event_time=None):
            """Update the energy sensor state."""
            self._state = self._state + self.calculate_delta(self._update_frequency)
            _LOGGER.debug(
                f"{self.entity_id}: Updating daily_fixed_energy sensor: {round(self._state, 4)}"
            )
            self.async_schedule_update_ha_state()

        self._timer = async_track_time_interval(
            self.hass, refresh, timedelta(seconds=self._update_frequency)
        )

    def calculate_delta(self, elapsedSeconds: int) -> Decimal:
        value = self._value
        if isinstance(value, Template):
            value = value.render()

        if self._unit_of_measurement == ENERGY_KILO_WATT_HOUR:
            kwhPerDay = value
        elif self._unit_of_measurement == POWER_WATT:
            kwhPerDay = (value * (self._on_time.total_seconds() / 3600)) / 1000

        return Decimal((kwhPerDay / 86400) * elapsedSeconds)

    @property
    def native_value(self):
        """Return the state of the sensor."""
        return round(self._state, self._rounding_digits)

    @callback
    def async_reset_energy(self) -> None:
        _LOGGER.debug(f"{self.entity_id}: Reset energy sensor")
        self._state = 0
        self.async_write_ha_state()

custom_components/powercalc/sensors/abstract.py METASEP

custom_components/powercalc/sensors/__init__.py METASEP

custom_components/powercalc/services.yaml METASEP
reset_energy:
  name: Reset energy sensor
  description: Reset an energy sensor to zero kWh
  target:
    entity:
      domain: sensor
custom_components/powercalc/manifest.json METASEP
{
  "domain": "powercalc",
  "name": "Powercalc",
  "documentation": "https://github.com/bramstroker/homeassistant-powercalc",
  "issue_tracker": "https://github.com/bramstroker/homeassistant-powercalc/issues",
  "dependencies": [
    "light",
    "group",
    "template",
    "select"
  ],
  "after_dependencies": [
    "integration",
    "utility_meter"
  ],
  "codeowners": ["@bramstroker"],
  "requirements": ["numpy>=1.21.1"],
  "version": "0.1.0",
  "iot_class": "local_polling"
}

custom_components/powercalc/sensor.py METASEP
"""Platform for sensor integration."""

from __future__ import annotations

import logging
import uuid
from typing import Final, cast

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from awesomeversion.awesomeversion import AwesomeVersion
from homeassistant.components import (
    binary_sensor,
    climate,
    device_tracker,
    fan,
    humidifier,
    input_boolean,
    input_number,
    input_select,
    light,
    media_player,
    remote,
    sensor,
    switch,
    vacuum,
    water_heater,
)
from homeassistant.components.group import DOMAIN as GROUP_DOMAIN
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.utility_meter import DEFAULT_OFFSET, max_28_days
from homeassistant.components.utility_meter.const import METER_TYPES
from homeassistant.const import (
    ATTR_ENTITY_ID,
    CONF_DOMAIN,
    CONF_ENTITIES,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    EVENT_HOMEASSISTANT_STARTED,
)
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.core import callback
from homeassistant.helpers import (
    area_registry,
    device_registry,
    entity_platform,
    entity_registry,
)
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import AddEntitiesCallback, split_entity_id
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import (
    ConfigType,
    DiscoveryInfoType,
    HomeAssistantType,
)

from .common import SourceEntity, create_source_entity, validate_name_pattern
from .const import (
    CALCULATION_MODES,
    CONF_AREA,
    CONF_CALCULATION_ENABLED_CONDITION,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_CREATE_ENERGY_SENSORS,
    CONF_CREATE_GROUP,
    CONF_CREATE_UTILITY_METERS,
    CONF_CUSTOM_MODEL_DIRECTORY,
    CONF_DAILY_FIXED_ENERGY,
    CONF_DISABLE_STANDBY_POWER,
    CONF_ENERGY_INTEGRATION_METHOD,
    CONF_ENERGY_SENSOR_CATEGORY,
    CONF_ENERGY_SENSOR_ID,
    CONF_ENERGY_SENSOR_NAMING,
    CONF_FIXED,
    CONF_GROUP,
    CONF_IGNORE_UNAVAILABLE_STATE,
    CONF_INCLUDE,
    CONF_LINEAR,
    CONF_MANUFACTURER,
    CONF_MODE,
    CONF_MODEL,
    CONF_MULTIPLY_FACTOR,
    CONF_MULTIPLY_FACTOR_STANDBY,
    CONF_POWER_SENSOR_CATEGORY,
    CONF_POWER_SENSOR_ID,
    CONF_POWER_SENSOR_NAMING,
    CONF_STANDBY_POWER,
    CONF_TEMPLATE,
    CONF_UTILITY_METER_OFFSET,
    CONF_UTILITY_METER_TARIFFS,
    CONF_UTILITY_METER_TYPES,
    CONF_WLED,
    DATA_CONFIGURED_ENTITIES,
    DATA_DISCOVERED_ENTITIES,
    DATA_DOMAIN_ENTITIES,
    DATA_USED_UNIQUE_IDS,
    DISCOVERY_SOURCE_ENTITY,
    DOMAIN,
    DOMAIN_CONFIG,
    DUMMY_ENTITY_ID,
    ENERGY_INTEGRATION_METHODS,
    ENTITY_CATEGORIES,
    SERVICE_RESET_ENERGY,
)
from .errors import (
    PowercalcSetupError,
    SensorAlreadyConfiguredError,
    SensorConfigurationError,
)
from .model_discovery import is_autoconfigurable
from .sensors.daily_energy import (
    DAILY_FIXED_ENERGY_SCHEMA,
    create_daily_fixed_energy_power_sensor,
    create_daily_fixed_energy_sensor,
)
from .sensors.energy import create_energy_sensor
from .sensors.group import create_group_sensors
from .sensors.power import RealPowerSensor, create_power_sensor
from .sensors.utility_meter import create_utility_meters
from .strategy.fixed import CONFIG_SCHEMA as FIXED_SCHEMA
from .strategy.linear import CONFIG_SCHEMA as LINEAR_SCHEMA
from .strategy.wled import CONFIG_SCHEMA as WLED_SCHEMA

_LOGGER = logging.getLogger(__name__)

SUPPORTED_ENTITY_DOMAINS = (
    light.DOMAIN,
    switch.DOMAIN,
    fan.DOMAIN,
    humidifier.DOMAIN,
    binary_sensor.DOMAIN,
    climate.DOMAIN,
    device_tracker.DOMAIN,
    remote.DOMAIN,
    media_player.DOMAIN,
    input_boolean.DOMAIN,
    input_number.DOMAIN,
    input_select.DOMAIN,
    sensor.DOMAIN,
    vacuum.DOMAIN,
    water_heater.DOMAIN,
)

MAX_GROUP_NESTING_LEVEL = 5

SENSOR_CONFIG = {
    vol.Optional(CONF_NAME): cv.string,
    vol.Optional(CONF_ENTITY_ID): cv.entity_domain(SUPPORTED_ENTITY_DOMAINS),
    vol.Optional(CONF_UNIQUE_ID): cv.string,
    vol.Optional(CONF_MODEL): cv.string,
    vol.Optional(CONF_MANUFACTURER): cv.string,
    vol.Optional(CONF_MODE): vol.In(CALCULATION_MODES),
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_DISABLE_STANDBY_POWER, default=False): cv.boolean,
    vol.Optional(CONF_CUSTOM_MODEL_DIRECTORY): cv.string,
    vol.Optional(CONF_POWER_SENSOR_ID): cv.entity_id,
    vol.Optional(CONF_FIXED): FIXED_SCHEMA,
    vol.Optional(CONF_LINEAR): LINEAR_SCHEMA,
    vol.Optional(CONF_WLED): WLED_SCHEMA,
    vol.Optional(CONF_DAILY_FIXED_ENERGY): DAILY_FIXED_ENERGY_SCHEMA,
    vol.Optional(CONF_CREATE_ENERGY_SENSOR): cv.boolean,
    vol.Optional(CONF_CREATE_UTILITY_METERS): cv.boolean,
    vol.Optional(CONF_UTILITY_METER_TARIFFS): vol.All(cv.ensure_list, [cv.string]),
    vol.Optional(CONF_UTILITY_METER_TYPES): vol.All(
        cv.ensure_list, [vol.In(METER_TYPES)]
    ),
    vol.Optional(CONF_UTILITY_METER_OFFSET, default=DEFAULT_OFFSET): vol.All(
        cv.time_period, cv.positive_timedelta, max_28_days
    ),
    vol.Optional(CONF_MULTIPLY_FACTOR): vol.Coerce(float),
    vol.Optional(CONF_MULTIPLY_FACTOR_STANDBY, default=False): cv.boolean,
    vol.Optional(CONF_POWER_SENSOR_NAMING): validate_name_pattern,
    vol.Optional(CONF_POWER_SENSOR_CATEGORY): vol.In(ENTITY_CATEGORIES),
    vol.Optional(CONF_ENERGY_SENSOR_ID): cv.entity_id,
    vol.Optional(CONF_ENERGY_SENSOR_NAMING): validate_name_pattern,
    vol.Optional(CONF_ENERGY_SENSOR_CATEGORY): vol.In(ENTITY_CATEGORIES),
    vol.Optional(CONF_ENERGY_INTEGRATION_METHOD): vol.In(ENERGY_INTEGRATION_METHODS),
    vol.Optional(CONF_CREATE_GROUP): cv.string,
    vol.Optional(CONF_INCLUDE, default={}): vol.Schema(
        {
            vol.Optional(CONF_AREA): cv.string,
            vol.Optional(CONF_GROUP): cv.entity_id,
            vol.Optional(CONF_TEMPLATE): cv.template,
            vol.Optional(CONF_DOMAIN): cv.string,
        }
    ),
    vol.Optional(CONF_IGNORE_UNAVAILABLE_STATE, default=False): cv.boolean,
    vol.Optional(CONF_CALCULATION_ENABLED_CONDITION): cv.template,
}


def build_nested_configuration_schema(schema: dict, iteration: int = 0) -> dict:
    if iteration == MAX_GROUP_NESTING_LEVEL:
        return schema
    iteration += 1
    schema.update(
        {
            vol.Optional(CONF_ENTITIES): vol.All(
                cv.ensure_list,
                [build_nested_configuration_schema(schema.copy(), iteration)],
            )
        }
    )
    return schema


SENSOR_CONFIG = build_nested_configuration_schema(SENSOR_CONFIG)

PLATFORM_SCHEMA: Final = vol.All(
    cv.has_at_least_one_key(
        CONF_ENTITY_ID, CONF_ENTITIES, CONF_INCLUDE, CONF_DAILY_FIXED_ENERGY
    ),
    PLATFORM_SCHEMA.extend(SENSOR_CONFIG),
)

ENTITY_ID_FORMAT = SENSOR_DOMAIN + ".{}"


async def async_setup_platform(
    hass: HomeAssistantType,
    config: ConfigType,
    async_add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None = None,
):
    """Set up the virtual power sensors."""

    platform = entity_platform.async_get_current_platform()
    platform.async_register_entity_service(
        SERVICE_RESET_ENERGY,
        {},
        "async_reset_energy",
    )

    try:
        entities = await create_sensors(hass, config, discovery_info)
    except SensorConfigurationError as err:
        _LOGGER.error(err)
        return

    if entities:
        async_add_entities(
            [entity for entity in entities[0] if isinstance(entity, SensorEntity)]
        )


def get_merged_sensor_configuration(*configs: dict, validate: bool = True) -> dict:
    """Merges configuration from multiple levels (sensor, group, global) into a single dict"""

    exclude_from_merging = [
        CONF_NAME,
        CONF_ENTITY_ID,
        CONF_UNIQUE_ID,
        CONF_POWER_SENSOR_ID,
    ]
    num_configs = len(configs)

    merged_config = {}
    for i, config in enumerate(configs, 1):
        config_copy = config.copy()
        # Remove config properties which are only allowed on the deepest level
        if i < num_configs:
            for key in exclude_from_merging:
                if key in config:
                    config_copy.pop(key)

        merged_config.update(config_copy)

    if not CONF_CREATE_ENERGY_SENSOR in merged_config:
        merged_config[CONF_CREATE_ENERGY_SENSOR] = merged_config.get(
            CONF_CREATE_ENERGY_SENSORS
        )

    if CONF_DAILY_FIXED_ENERGY in merged_config:
        merged_config[CONF_ENTITY_ID] = DUMMY_ENTITY_ID

    if validate:
        if (
            not CONF_CREATE_GROUP in merged_config
            and not CONF_ENTITY_ID in merged_config
        ):
            raise SensorConfigurationError(
                "You must supply an entity_id in the configuration, see the README"
            )

    return merged_config


async def create_sensors(
    hass: HomeAssistantType,
    config: ConfigType,
    discovery_info: DiscoveryInfoType | None = None,
) -> tuple(list[SensorEntity, RealPowerSensor], list[SensorEntity, RealPowerSensor]):
    """Main routine to create all sensors (power, energy, utility, group) for a given entity"""

    global_config = hass.data[DOMAIN][DOMAIN_CONFIG]

    if CONF_DAILY_FIXED_ENERGY in config:
        config[CONF_ENTITY_ID] = DUMMY_ENTITY_ID

    # Setup a power sensor for one single appliance. Either by manual configuration or discovery
    if CONF_ENTITY_ID in config or discovery_info is not None:
        if discovery_info:
            config[CONF_ENTITY_ID] = discovery_info[CONF_ENTITY_ID]
        merged_sensor_config = get_merged_sensor_configuration(global_config, config)
        new_sensors = await create_individual_sensors(
            hass, merged_sensor_config, discovery_info
        )
        return (new_sensors, [])

    # Setup power sensors for multiple appliances in one config entry
    sensor_configs = {}
    new_sensors = []
    existing_sensors = []
    if CONF_ENTITIES in config:
        for entity_config in config[CONF_ENTITIES]:
            # When there are nested entities, combine these with the current entities, resursively
            if CONF_ENTITIES in entity_config or CONF_CREATE_GROUP in entity_config:
                try:
                    (child_new_sensors, child_existing_sensors) = await create_sensors(
                        hass, entity_config
                    )
                except SensorConfigurationError as err:
                    _LOGGER.error(err)
                    continue
                
                new_sensors.extend(child_new_sensors)
                existing_sensors.extend(child_existing_sensors)
                continue

            entity_id = entity_config.get(CONF_ENTITY_ID) or str(uuid.uuid4())
            sensor_configs.update({entity_id: entity_config})

    # Automatically add a bunch of entities by area or evaluating template
    if CONF_INCLUDE in config:
        entities = resolve_include_entities(hass, config.get(CONF_INCLUDE))
        sensor_configs = {
            entity.entity_id: {CONF_ENTITY_ID: entity.entity_id}
            for entity in entities
            if entity and await is_autoconfigurable(hass, entity)
        } | sensor_configs

    # Create sensors for each entity
    for sensor_config in sensor_configs.values():
        merged_sensor_config = get_merged_sensor_configuration(
            global_config, config, sensor_config
        )
        try:
            new_sensors.extend(
                await create_individual_sensors(hass, merged_sensor_config)
            )
        except SensorConfigurationError as error:
            _LOGGER.error(error)

    if not new_sensors and not existing_sensors:
        if CONF_CREATE_GROUP in config:
            raise SensorConfigurationError(
                f"Could not resolve any entities in group '{config.get(CONF_CREATE_GROUP)}'"
            )
        elif not sensor_configs:
            raise SensorConfigurationError(
                f"Could not resolve any entities for non-group sensor"
            )

    # Create group sensors (power, energy, utility)
    if CONF_CREATE_GROUP in config:
        group_entities = new_sensors + existing_sensors
        group_name = config.get(CONF_CREATE_GROUP)
        if not group_entities:
            _LOGGER.error("Could not create group %s, no entities resolved", group_name)
        group_sensors = await create_group_sensors(
            group_name,
            get_merged_sensor_configuration(global_config, config, validate=False),
            group_entities,
            hass=hass,
        )
        new_sensors.extend(group_sensors)

    return (new_sensors, existing_sensors)


async def create_individual_sensors(
    hass: HomeAssistantType,
    sensor_config: dict,
    discovery_info: DiscoveryInfoType | None = None,
) -> list[SensorEntity, RealPowerSensor]:
    """Create entities (power, energy, utility_meters) which track the appliance."""

    if discovery_info:
        source_entity: SourceEntity = discovery_info.get(DISCOVERY_SOURCE_ENTITY)
    else:
        source_entity = await create_source_entity(sensor_config[CONF_ENTITY_ID], hass)

    if (used_unique_ids := hass.data.get(DATA_USED_UNIQUE_IDS)) is None:
        used_unique_ids = hass.data[DATA_USED_UNIQUE_IDS] = []
    try:
        check_entity_not_already_configured(
            sensor_config, source_entity, hass, used_unique_ids
        )
    except SensorAlreadyConfiguredError as error:
        if discovery_info:
            return []
        raise error

    entities_to_add = []

    energy_sensor = None
    if CONF_DAILY_FIXED_ENERGY in sensor_config:
        energy_sensor = await create_daily_fixed_energy_sensor(hass, sensor_config)
        entities_to_add.append(energy_sensor)
        power_sensor = await create_daily_fixed_energy_power_sensor(
            hass, sensor_config, source_entity
        )
        if power_sensor:
            entities_to_add.append(power_sensor)

    else:
        try:
            power_sensor = await create_power_sensor(
                hass, sensor_config, source_entity, discovery_info
            )
        except PowercalcSetupError:
            return []

        entities_to_add.append(power_sensor)

        # Create energy sensor which integrates the power sensor
        if sensor_config.get(CONF_CREATE_ENERGY_SENSOR):
            energy_sensor = await create_energy_sensor(
                hass, sensor_config, power_sensor, source_entity
            )
            entities_to_add.append(energy_sensor)

    if energy_sensor:
        entities_to_add.extend(
            await create_utility_meters(hass, energy_sensor, sensor_config)
        )

    if source_entity.entity_entry and source_entity.device_entry:
        hass.bus.async_listen_once(
            EVENT_HOMEASSISTANT_STARTED,
            callback(
                lambda _: bind_entities_to_devices(
                    hass,
                    entities_to_add,
                    source_entity.device_entry.id,
                )
            ),
        )

    # Update several registries
    if discovery_info:
        hass.data[DOMAIN][DATA_DISCOVERED_ENTITIES].append(source_entity.entity_id)
    else:
        hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES].update(
            {source_entity.entity_id: entities_to_add}
        )

    if not source_entity.domain in hass.data[DOMAIN][DATA_DOMAIN_ENTITIES]:
        hass.data[DOMAIN][DATA_DOMAIN_ENTITIES][source_entity.domain] = []

    hass.data[DOMAIN][DATA_DOMAIN_ENTITIES][source_entity.domain].extend(
        entities_to_add
    )

    # Keep track for which unique_id's we generated sensors already
    unique_id = sensor_config.get(CONF_UNIQUE_ID) or source_entity.unique_id
    if unique_id:
        used_unique_ids.append(unique_id)

    return entities_to_add


def check_entity_not_already_configured(
    sensor_config: dict,
    source_entity: SourceEntity,
    hass: HomeAssistantType,
    used_unique_ids: list[str],
):
    if source_entity.entity_id == DUMMY_ENTITY_ID:
        return

    unique_id = sensor_config.get(CONF_UNIQUE_ID) or source_entity.unique_id
    if unique_id and unique_id in used_unique_ids:
        raise SensorAlreadyConfiguredError(source_entity.entity_id)

    if (
        unique_id is None
        and source_entity.entity_id in hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES]
    ):
        raise SensorAlreadyConfiguredError(source_entity.entity_id)


def bind_entities_to_devices(hass: HomeAssistantType, entities, device_id: str):
    """Attach all the power/energy sensors to the same device as the source entity"""

    if AwesomeVersion(HA_VERSION) < AwesomeVersion("2022.2"):
        return

    for entity in entities:
        ent_reg = entity_registry.async_get(hass)
        entity_entry = ent_reg.async_get(entity.entity_id)
        if (
            not entity_entry
            or entity_entry.platform != DOMAIN
            or entity_entry.device_id == device_id
        ):
            continue

        _LOGGER.debug(f"Binding {entity.entity_id} to device {device_id}")
        ent_reg.async_update_entity(entity.entity_id, device_id=device_id)


@callback
def resolve_include_entities(
    hass: HomeAssistantType, include_config: dict
) -> list[entity_registry.RegistryEntry]:
    entities = {}
    entity_reg = entity_registry.async_get(hass)

    # Include entities from a certain area
    if CONF_AREA in include_config:
        area_id = include_config.get(CONF_AREA)
        _LOGGER.debug("Including entities from area: %s", area_id)
        entities = entities | resolve_area_entities(hass, area_id)

    # Include entities from a certain domain
    if CONF_DOMAIN in include_config:
        domain = include_config.get(CONF_DOMAIN)
        _LOGGER.debug("Including entities from domain: %s", domain)
        entities = entities | {
            entity.entity_id: entity
            for entity in entity_reg.entities.values()
            if entity.domain == domain
        }

    # Include entities from a certain group
    if CONF_GROUP in include_config:
        group_id = include_config.get(CONF_GROUP)
        _LOGGER.debug("Including entities from area: %s", group_id)
        entities = entities | resolve_include_groups(hass, group_id)

    # Include entities by evaluating a template
    if CONF_TEMPLATE in include_config:
        template = include_config.get(CONF_TEMPLATE)
        if not isinstance(template, Template):
            raise SensorConfigurationError(
                "include->template is not a correct Template"
            )
        template.hass = hass

        _LOGGER.debug("Including entities from template")
        entity_ids = template.async_render()
        entities = entities | {
            entity_id: entity_reg.async_get(entity_id) for entity_id in entity_ids
        }

    return entities.values()


@callback
def resolve_include_groups(
    hass: HomeAssistantType, group_id: str
) -> dict[str, entity_registry.RegistryEntry]:
    """Get a listing of al entities in a given group"""
    entity_reg = entity_registry.async_get(hass)

    domain = split_entity_id(group_id)[0]
    if domain == LIGHT_DOMAIN:
        light_component = cast(EntityComponent, hass.data.get(LIGHT_DOMAIN))
        light_group = next(
            filter(
                lambda entity: entity.entity_id == group_id, light_component.entities
            ),
            None,
        )
        if light_group is None or light_group.platform.platform_name != GROUP_DOMAIN:
            raise SensorConfigurationError(f"Light group {group_id} not found")

        entity_ids = light_group.extra_state_attributes.get(ATTR_ENTITY_ID)
    else:
        group_state = hass.states.get(group_id)
        entity_ids = group_state.attributes.get(ATTR_ENTITY_ID)

    return {entity_id: entity_reg.async_get(entity_id) for entity_id in entity_ids}


@callback
def resolve_area_entities(
    hass: HomeAssistantType, area_id_or_name: str
) -> dict[str, entity_registry.RegistryEntry]:
    """Get a listing of al entities in a given area"""
    area_reg = area_registry.async_get(hass)
    area = area_reg.async_get_area(area_id_or_name)
    if area is None:
        area = area_reg.async_get_area_by_name(str(area_id_or_name))

    if area is None:
        raise SensorConfigurationError(
            f"No area with id or name '{area_id_or_name}' found in your HA instance"
        )

    area_id = area.id
    entity_reg = entity_registry.async_get(hass)

    entities = entity_registry.async_entries_for_area(entity_reg, area_id)

    device_reg = device_registry.async_get(hass)
    # We also need to add entities tied to a device in the area that don't themselves
    # have an area specified since they inherit the area from the device.
    entities.extend(
        [
            entity
            for device in device_registry.async_entries_for_area(device_reg, area_id)
            for entity in entity_registry.async_entries_for_device(
                entity_reg, device.id
            )
            if entity.area_id is None
        ]
    )
    return {
        entity.entity_id: entity for entity in entities if entity.domain == LIGHT_DOMAIN
    }

custom_components/powercalc/model_discovery.py METASEP
"""Utilities for auto discovery of light models."""

from __future__ import annotations

import logging
import os
import re
from typing import NamedTuple, Optional

import homeassistant.helpers.device_registry as dr
import homeassistant.helpers.entity_registry as er
from homeassistant.helpers.typing import HomeAssistantType

from .aliases import MANUFACTURER_ALIASES
from .const import CONF_CUSTOM_MODEL_DIRECTORY, CONF_MANUFACTURER, CONF_MODEL
from .errors import ModelNotSupported
from .light_model import LightModel

_LOGGER = logging.getLogger(__name__)


async def get_light_model(
    hass: HomeAssistantType,
    config: dict,
    entity_entry: Optional[er.RegistryEntry] = None,
) -> Optional[LightModel]:
    manufacturer = config.get(CONF_MANUFACTURER)
    model = config.get(CONF_MODEL)
    if (manufacturer is None or model is None) and entity_entry:
        model_info = await autodiscover_model(hass, entity_entry)
        if model_info:
            manufacturer = config.get(CONF_MANUFACTURER) or model_info.manufacturer
            model = config.get(CONF_MODEL) or model_info.model

    if manufacturer is None or model is None:
        return None

    custom_model_directory = config.get(CONF_CUSTOM_MODEL_DIRECTORY)
    if custom_model_directory:
        custom_model_directory = os.path.join(
            hass.config.config_dir, custom_model_directory
        )

    return LightModel(hass, manufacturer, model, custom_model_directory)


async def is_autoconfigurable(
    hass: HomeAssistantType, entry: er.RegistryEntry, sensor_config: dict = {}
) -> bool:
    try:
        light_model = await get_light_model(hass, sensor_config, entry)
        return bool(
            light_model and not light_model.is_additional_configuration_required
        )
    except ModelNotSupported:
        return False


async def autodiscover_model(
    hass: HomeAssistantType, entity_entry: er.RegistryEntry
) -> Optional[ModelInfo]:
    """Try to auto discover manufacturer and model from the known device information"""

    if not await has_manufacturer_and_model_information(hass, entity_entry):
        _LOGGER.debug(
            "%s: Cannot autodiscover model, manufacturer or model unknown from device registry",
            entity_entry.entity_id,
        )
        return None

    device_registry = dr.async_get(hass)
    device_entry = device_registry.async_get(entity_entry.device_id)
    model_id = device_entry.model
    if match := re.search("\(([^\(\)]+)\)$", str(device_entry.model)):
        model_id = match.group(1)

    manufacturer = device_entry.manufacturer
    if MANUFACTURER_ALIASES.get(manufacturer):
        manufacturer = MANUFACTURER_ALIASES.get(manufacturer)

    # Make sure we don't have a literal / in model_id, so we don't get issues with sublut directory matching down the road
    # See github #658
    model_id = model_id.replace("/", "#slash#")

    model_info = ModelInfo(manufacturer, model_id)

    _LOGGER.debug(
        "%s: Auto discovered model (manufacturer=%s, model=%s)",
        entity_entry.entity_id,
        model_info.manufacturer,
        model_info.model,
    )
    return model_info


async def has_manufacturer_and_model_information(
    hass: HomeAssistantType, entity_entry: er.RegistryEntry | None
):
    """See if we have enough information in device registry to automatically setup the power sensor"""

    if entity_entry is None:
        return False

    device_registry = dr.async_get(hass)
    device_entry = device_registry.async_get(entity_entry.device_id)
    if device_entry is None:
        return False

    if device_entry.manufacturer is None or device_entry.model is None:
        return False

    return True


class ModelInfo(NamedTuple):
    manufacturer: str
    model: str

custom_components/powercalc/migrate.py METASEP
from __future__ import annotations

import logging

from homeassistant.core import callback
from homeassistant.helpers.entity_registry import EntityRegistry, async_get

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)


@callback
def async_migrate_unique_id(
    ent_reg: EntityRegistry, platform: str, old_unique_id: str, new_unique_id: str
) -> None:
    """Check if entity with old unique ID exists, and if so migrate it to new ID."""
    if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id):
        _LOGGER.debug(
            "Migrating entity %s from old unique ID '%s' to new unique ID '%s'",
            entity_id,
            old_unique_id,
            new_unique_id,
        )
        try:
            ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
        except ValueError:
            _LOGGER.debug(
                (
                    "Entity %s can't be migrated because the unique ID is taken; "
                    "Cleaning it up since it is likely no longer valid"
                ),
                entity_id,
            )
            ent_reg.async_remove(entity_id)


@callback
def async_migrate_entity_id(
    hass, platform: str, unique_id: str, new_entity_id: str
) -> None:
    """Check if entity with old unique ID exists, and if so migrate it to new ID."""

    entity_registry = async_get(hass)

    existing_entity_id = entity_registry.async_get_entity_id(
        platform, DOMAIN, unique_id
    )
    if existing_entity_id is None or existing_entity_id == new_entity_id:
        return

    _LOGGER.debug(
        "Migrating entity from old entity ID '%s' to new entity ID '%s'",
        existing_entity_id,
        new_entity_id,
    )
    try:
        entity_registry.async_update_entity(
            existing_entity_id, new_entity_id=new_entity_id
        )
    except ValueError as e:
        _LOGGER.error(e)
        entity_registry.async_remove(new_entity_id)


@callback
def async_set_unique_id(hass, entity_id: str, unique_id: str) -> None:
    """Set a new unique id for a given entity id."""
    entity_registry = async_get(hass)

    entry = entity_registry.async_get(entity_id)
    if entry and entry.unique_id != unique_id:
        _LOGGER.debug(
            "Migrating entity %s to new unique ID '%s'",
            entity_id,
            unique_id,
        )
        entity_registry.async_update_entity(entity_id, new_unique_id=unique_id)

custom_components/powercalc/light_model.py METASEP
import json
import logging
import os
from enum import Enum
from typing import Optional

from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.helpers.typing import HomeAssistantType

from .aliases import MANUFACTURER_DIRECTORY_MAPPING, MODEL_DIRECTORY_MAPPING
from .const import MODE_FIXED, MODE_LINEAR
from .errors import ModelNotSupported, UnsupportedMode

_LOGGER = logging.getLogger(__name__)

CUSTOM_DATA_DIRECTORY = "powercalc-custom-models"


class DeviceType(Enum):
    LIGHT = "light"
    SMART_SWITCH = "smart_switch"


class LightModel:
    def __init__(
        self,
        hass: HomeAssistantType,
        manufacturer: str,
        model: str,
        custom_model_directory: str,
    ):
        self._manufacturer = manufacturer
        self._model = model
        self._lut_subdirectory = None

        # Support multiple LUT in subdirectories
        if "/" in model:
            model_parts = model.split("/", 1)
            self._model = model_parts[0]
            self._lut_subdirectory = model_parts[1]

        self._model = self._model.replace("#slash#", "/")
        self._custom_model_directory = custom_model_directory
        self._hass = hass
        self._directory: str = None
        self.load_model_manifest()

    def load_model_manifest(self) -> dict:
        """Load the model.json file data containing information about the light model"""

        model_directory = self.get_directory()
        file_path = os.path.join(model_directory, "model.json")
        if not os.path.exists(file_path):
            raise ModelNotSupported(
                f"Model not found in library (manufacturer: {self._manufacturer}, model: {self._model})"
            )

        _LOGGER.debug(f"Loading {file_path}")
        json_file = open(file_path)
        self._json_data = json.load(json_file)

        if self._lut_subdirectory:
            subdirectory = os.path.join(self._directory, self._lut_subdirectory)
            _LOGGER.debug(f"Loading LUT directory {self._directory}")
            if not os.path.exists(file_path):
                raise ModelNotSupported(
                    f"LUT subdirectory not found (manufacturer: {self._manufacturer}, model: {self._model})"
                )

            # When the sub LUT directory also has a model.json (not required), merge this json into the main model.json data.
            file_path = os.path.join(subdirectory, "model.json")
            if os.path.exists(file_path):
                json_file = open(file_path)
                self._json_data = {**self._json_data, **json.load(json_file)}

        return self._json_data

    def get_directory(self) -> str:
        """
        Get the light model directory.
        Using the following fallback mechanism:
         - custom_model_directory defined on sensor configuration
         - check in user defined directory (config/powercalc-custom-models)
         - check in alternative user defined directory (config/custom_components/powercalc/custom_data)
         - check in buildin directory (config/custom_components/powercalc/data)
        """

        # Only fetch directory once
        if self._directory:
            return self._directory

        if self._custom_model_directory:
            return self._custom_model_directory

        manufacturer_directory = (
            MANUFACTURER_DIRECTORY_MAPPING.get(self._manufacturer) or self._manufacturer
        ).lower()

        model_directory = self._model
        if isinstance(
            MODEL_DIRECTORY_MAPPING.get(self._manufacturer), dict
        ) and MODEL_DIRECTORY_MAPPING.get(self._manufacturer).get(self._model):
            model_directory = MODEL_DIRECTORY_MAPPING.get(self._manufacturer).get(
                self._model
            )

        data_directories = (
            os.path.join(self._hass.config.config_dir, CUSTOM_DATA_DIRECTORY),
            os.path.join(os.path.dirname(__file__), "custom_data"),
            os.path.join(os.path.dirname(__file__), "data"),
        )
        for data_dir in data_directories:
            model_data_dir = os.path.join(
                data_dir,
                f"{manufacturer_directory}/{model_directory}",
            )
            if os.path.exists(model_data_dir):
                self._directory = model_data_dir
                return self._directory

        raise ModelNotSupported(
            f"Model not found in library (manufacturer: {self._manufacturer}, model: {self._model})"
        )

    def get_lut_directory(self) -> str:
        if self.linked_lut:
            return os.path.join(os.path.dirname(__file__), "data", self.linked_lut)

        model_directory = self.get_directory()
        if self._lut_subdirectory:
            model_directory = os.path.join(model_directory, self._lut_subdirectory)
        return model_directory

    @property
    def manufacturer(self) -> str:
        return self._manufacturer

    @property
    def model(self) -> str:
        return self._model

    @property
    def name(self) -> str:
        return self._json_data.get("name")

    @property
    def standby_power(self) -> float:
        return self._json_data.get("standby_power") or 0

    @property
    def standby_power_on(self) -> float:
        return self._json_data.get("standby_power_on") or 0

    @property
    def supported_modes(self) -> list:
        return self._json_data.get("supported_modes") or []

    @property
    def linked_lut(self) -> Optional[str]:
        return self._json_data.get("linked_lut")

    @property
    def calculation_enabled_condition(self) -> Optional[str]:
        return self._json_data.get("calculation_enabled_condition")

    @property
    def linear_mode_config(self) -> Optional[dict]:
        if not self.is_mode_supported(MODE_LINEAR):
            raise UnsupportedMode(
                f"Mode linear is not supported by model: {self._model}"
            )
        return self._json_data.get("linear_config")

    @property
    def fixed_mode_config(self) -> Optional[dict]:
        if not self.is_mode_supported(MODE_FIXED):
            raise UnsupportedMode(
                f"Mode fixed is not supported by model: {self._model}"
            )
        return self._json_data.get("fixed_config")

    def is_mode_supported(self, mode: str) -> bool:
        return mode in self.supported_modes

    @property
    def is_additional_configuration_required(self) -> bool:
        if self._lut_subdirectory is not None:
            return True
        return self._json_data.get("requires_additional_configuration") or False

    @property
    def device_type(self) -> str:
        return self._json_data.get("device_type") or DeviceType.LIGHT

    def is_entity_domain_supported(self, domain: str) -> bool:
        """Check whether this power profile supports a given entity domain"""
        if domain == LIGHT_DOMAIN and self.device_type != DeviceType.LIGHT:
            return False

        return True

custom_components/powercalc/helpers.py METASEP
from decimal import Decimal
from typing import Union

from homeassistant.helpers.template import Template


async def evaluate_power(power: Union[Template, Decimal]) -> Decimal:
    """When power is a template render it."""

    if isinstance(power, Template):
        return power.async_render()

    return Decimal(power)

custom_components/powercalc/errors.py METASEP
"""Errors for the power component."""
from homeassistant.components.sensor import SensorEntity
from homeassistant.exceptions import HomeAssistantError


class PowercalcSetupError(HomeAssistantError):
    """Raised when an error occured during powercalc sensor setup."""


class SensorConfigurationError(PowercalcSetupError):
    """Raised when sensor configuration is invalid"""


class SensorAlreadyConfiguredError(SensorConfigurationError):
    """Raised when power sensors has already been configured before for the entity"""

    def __init__(self, source_entity_id: str):
        super().__init__(
            f"{source_entity_id}: This entity has already configured a power sensor. When you want to configure it twice make sure to give it a unique_id"
        )


class StrategyConfigurationError(PowercalcSetupError):
    """Raised when strategy is not setup correctly."""


class ModelNotSupported(StrategyConfigurationError):
    """Raised when model is not supported."""


class UnsupportedMode(PowercalcSetupError):
    """Mode not supported."""


class LutFileNotFound(PowercalcSetupError):
    """Raised when LUT CSV file does not exist"""

custom_components/powercalc/const.py METASEP
"""The Powercalc constants."""

from datetime import timedelta

from homeassistant.components.utility_meter.const import DAILY, MONTHLY, WEEKLY
from homeassistant.const import (
    STATE_NOT_HOME,
    STATE_OFF,
    STATE_STANDBY,
    STATE_UNAVAILABLE,
)

MIN_HA_VERSION = "2021.12"

DOMAIN = "powercalc"
DOMAIN_CONFIG = "config"

DATA_CALCULATOR_FACTORY = "calculator_factory"
DATA_CONFIGURED_ENTITIES = "configured_entities"
DATA_DISCOVERED_ENTITIES = "discovered_entities"
DATA_DOMAIN_ENTITIES = "domain_entities"
DATA_USED_UNIQUE_IDS = "used_unique_ids"

DUMMY_ENTITY_ID = "sensor.dummy"

CONF_AREA = "area"
CONF_CALIBRATE = "calibrate"
CONF_CREATE_GROUP = "create_group"
CONF_CREATE_DOMAIN_GROUPS = "create_domain_groups"
CONF_CREATE_ENERGY_SENSOR = "create_energy_sensor"
CONF_CREATE_ENERGY_SENSORS = "create_energy_sensors"
CONF_CREATE_UTILITY_METERS = "create_utility_meters"
CONF_DAILY_FIXED_ENERGY = "daily_fixed_energy"
CONF_ENABLE_AUTODISCOVERY = "enable_autodiscovery"
CONF_ENERGY_INTEGRATION_METHOD = "energy_integration_method"
CONF_ENERGY_SENSOR_CATEGORY = "energy_sensor_category"
CONF_ENERGY_SENSOR_ID = "energy_sensor_id"
CONF_ENERGY_SENSOR_NAMING = "energy_sensor_naming"
CONF_ENERGY_SENSOR_FRIENDLY_NAMING = "energy_sensor_friendly_naming"
CONF_ENERGY_SENSOR_PRECISION = "energy_sensor_precision"
CONF_FIXED = "fixed"
CONF_GROUP = "group"
CONF_GAMMA_CURVE = "gamma_curve"
CONF_IGNORE_UNAVAILABLE_STATE = "ignore_unavailable_state"
CONF_INCLUDE = "include"
CONF_LINEAR = "linear"
CONF_MODEL = "model"
CONF_MANUFACTURER = "manufacturer"
CONF_MODE = "mode"
CONF_MULTIPLY_FACTOR = "multiply_factor"
CONF_MULTIPLY_FACTOR_STANDBY = "multiply_factor_standby"
CONF_POWER_FACTOR = "power_factor"
CONF_POWER_SENSOR_CATEGORY = "power_sensor_category"
CONF_POWER_SENSOR_NAMING = "power_sensor_naming"
CONF_POWER_SENSOR_FRIENDLY_NAMING = "power_sensor_friendly_naming"
CONF_POWER_SENSOR_PRECISION = "power_sensor_precision"
CONF_POWER = "power"
CONF_POWER_SENSOR_ID = "power_sensor_id"
CONF_MIN_POWER = "min_power"
CONF_MAX_POWER = "max_power"
CONF_ON_TIME = "on_time"
CONF_TEMPLATE = "template"
CONF_UPDATE_FREQUENCY = "update_frequency"
CONF_VALUE = "value"
CONF_VOLTAGE = "voltage"
CONF_WLED = "wled"
CONF_STATES_POWER = "states_power"
CONF_START_TIME = "start_time"
CONF_STANDBY_POWER = "standby_power"
CONF_CALCULATION_ENABLED_CONDITION = "calculation_enabled_condition"
CONF_DISABLE_STANDBY_POWER = "disable_standby_power"
CONF_CUSTOM_MODEL_DIRECTORY = "custom_model_directory"
CONF_UTILITY_METER_OFFSET = "utility_meter_offset"
CONF_UTILITY_METER_TYPES = "utility_meter_types"
CONF_UTILITY_METER_TARIFFS = "utility_meter_tariffs"

# Redefine constants from integration component.
# Has been refactored in HA 2022.4, we need to support older HA versions as well.
ENERGY_INTEGRATION_METHOD_LEFT = "left"
ENERGY_INTEGRATION_METHOD_RIGHT = "right"
ENERGY_INTEGRATION_METHOD_TRAPEZODIAL = "trapezoidal"
ENERGY_INTEGRATION_METHODS = [
    ENERGY_INTEGRATION_METHOD_LEFT,
    ENERGY_INTEGRATION_METHOD_RIGHT,
    ENERGY_INTEGRATION_METHOD_TRAPEZODIAL,
]

ENTITY_CATEGORY_CONFIG = "config"
ENTITY_CATEGORY_DIAGNOSTIC = "diagnostic"
ENTITY_CATEGORY_NONE = None
ENTITY_CATEGORY_SYSTEM = "system"
ENTITY_CATEGORIES = [
    ENTITY_CATEGORY_CONFIG,
    ENTITY_CATEGORY_DIAGNOSTIC,
    ENTITY_CATEGORY_NONE,
    ENTITY_CATEGORY_SYSTEM,
]

DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
DEFAULT_POWER_NAME_PATTERN = "{} power"
DEFAULT_POWER_SENSOR_PRECISION = 2
DEFAULT_ENERGY_INTEGRATION_METHOD = ENERGY_INTEGRATION_METHOD_TRAPEZODIAL
DEFAULT_ENERGY_NAME_PATTERN = "{} energy"
DEFAULT_ENERGY_SENSOR_PRECISION = 4
DEFAULT_ENTITY_CATEGORY = ENTITY_CATEGORY_NONE
DEFAULT_UTILITY_METER_TYPES = [DAILY, WEEKLY, MONTHLY]

DISCOVERY_SOURCE_ENTITY = "source_entity"
DISCOVERY_LIGHT_MODEL = "light_model"

ATTR_CALCULATION_MODE = "calculation_mode"
ATTR_ENTITIES = "entities"
ATTR_INTEGRATION = "integration"
ATTR_IS_GROUP = "is_group"
ATTR_SOURCE_ENTITY = "source_entity"
ATTR_SOURCE_DOMAIN = "source_domain"

SERVICE_RESET_ENERGY = "reset_energy"

MODE_DAILY_FIXED_ENERGY = "daily_fixed_energy"
MODE_LUT = "lut"
MODE_LINEAR = "linear"
MODE_FIXED = "fixed"
MODE_WLED = "wled"
CALCULATION_MODES = [
    MODE_DAILY_FIXED_ENERGY,
    MODE_FIXED,
    MODE_LINEAR,
    MODE_LUT,
    MODE_WLED,
]

OFF_STATES = (STATE_OFF, STATE_NOT_HOME, STATE_STANDBY, STATE_UNAVAILABLE)

custom_components/powercalc/common.py METASEP
from __future__ import annotations

import re
from typing import NamedTuple

import homeassistant.helpers.device_registry as dr
import homeassistant.helpers.entity_registry as er
import voluptuous as vol
from homeassistant.components.light import ATTR_SUPPORTED_COLOR_MODES
from homeassistant.core import split_entity_id
from homeassistant.helpers.typing import HomeAssistantType

from .const import DUMMY_ENTITY_ID


class SourceEntity(NamedTuple):
    object_id: str
    entity_id: str
    domain: str
    unique_id: str | None = None
    name: str | None = None
    supported_color_modes: list | None = None
    entity_entry: er.RegistryEntry | None = None
    device_entry: dr.DeviceEntry | None = None


async def create_source_entity(entity_id: str, hass: HomeAssistantType) -> SourceEntity:
    """Create object containing all information about the source entity"""

    if entity_id == DUMMY_ENTITY_ID:
        return SourceEntity(
            object_id=DUMMY_ENTITY_ID, entity_id=DUMMY_ENTITY_ID, domain=DUMMY_ENTITY_ID
        )

    source_entity_domain, source_object_id = split_entity_id(entity_id)

    entity_registry = er.async_get(hass)
    entity_entry = entity_registry.async_get(entity_id)

    dev = dr.async_get(hass)
    if entity_entry and entity_entry.device_id:
        device_entry = dev.async_get(entity_entry.device_id)
    else:
        device_entry = None

    unique_id = None
    supported_color_modes = []
    if entity_entry:
        source_entity_name = entity_entry.name or entity_entry.original_name
        source_entity_domain = entity_entry.domain
        unique_id = entity_entry.unique_id
        if entity_entry.capabilities:
            supported_color_modes = entity_entry.capabilities.get(
                ATTR_SUPPORTED_COLOR_MODES
            )
    else:
        source_entity_name = source_object_id.replace("_", " ")

    entity_state = hass.states.get(entity_id)
    if entity_state:
        source_entity_name = entity_state.name
        supported_color_modes = entity_state.attributes.get(ATTR_SUPPORTED_COLOR_MODES)

    return SourceEntity(
        source_object_id,
        entity_id,
        source_entity_domain,
        unique_id,
        source_entity_name,
        supported_color_modes or [],
        entity_entry,
        device_entry,
    )


def validate_name_pattern(value: str) -> str:
    """Validate that the naming pattern contains {}."""
    regex = re.compile(r"\{\}")
    if not regex.search(value):
        raise vol.Invalid("Naming pattern must contain {}")
    return value

custom_components/powercalc/aliases.py METASEP
"""Aliases for manufacturers and model IDs."""

MANUFACTURER_ALIASES = {
    "Philips": "Signify Netherlands B.V.",
    "IKEA": "IKEA of Sweden",
    "Xiaomi": "Aqara",
    "LUMI": "Aqara",
    "ADEO": "Lexman",
    "MLI": "Müller Licht",
    "LightZone": "MeLiTec",
}

MANUFACTURER_DIRECTORY_MAPPING = {
    "IKEA of Sweden": "ikea",
    "Feibit Inc co.  ": "jiawen",
    "LEDVANCE": "ledvance",
    "Müller Licht": "mueller-licht",
    "OSRAM": "osram",
    "Signify Netherlands B.V.": "signify",
    "Aqara": "aqara",
    "Lexman": "lexman",
    "Yeelight": "yeelight",
    "TuYa": "tuya",
    "MeLiTec": "melitec",
    "WiZ": "wiz",
}

MODEL_DIRECTORY_MAPPING = {
    "IKEA of Sweden": {
        "FLOALT panel WS 30x30": "L1527",
        "FLOALT panel WS 60x60": "L1529",
        "Slagsida": "L1616",
        "TRADFRI bulb E14 WS opal 400lm": "LED1536G5",
        "TRADFRI bulb GU10 WS 400lm": "LED1537R6",
        "TRADFRI bulb E27 WS opal 980lm": "LED1545G12",
        "TRADFRI bulb E27 WS clear 950lm": "LED1546G12",
        "TRADFRI bulb E27 opal 1000lm": "LED1623G12",
        "TRADFRI bulb E27 W opal 1000lm": "LED1623G12",
        "TRADFRI bulb E14 CWS opal 600lm": "LED1624G9",
        "TRADFRI bulb E26 CWS opal 600lm": "LED1624G9",
        "TRADFRI bulb E27 CWS opal 600lm": "LED1624G9",
        "TRADFRI bulb E14 W op/ch 400lm": "LED1649C5",
        "TRADFRI bulb GU10 W 400lm": "LED1650R5",
        "TRADFRI bulb E27 WS opal 1000lm": "LED1732G11",
        "TRADFRI bulb E14 WS opal 600lm": "LED1733G7",
        "TRADFRI bulb E27 WS clear 806lm": "LED1736G9",
        "TRADFRI bulb E14 WS opal 600lm": "LED1738G7",
        "TRADFRI bulb E14 WS 470lm": "LED1835C6",
        "TRADFRI bulb E27 WW 806lm": "LED1836G9",
        "TRADFRI bulb E27 WW clear 250lm": "LED1842G3",
        "TRADFRI bulb GU10 WW 400lm": "LED1837R5",
        "TRADFRI bulb GU10 CWS 345lm": "LED1923R5",
        "TRADFRI bulb E27 CWS 806lm": "LED1924G9",
        "TRADFRI bulb E14 CWS 470lm": "LED1925G6",
        "TRADFRIbulbE14WScandleopal470lm": "LED1949C5",
        "TRADFRIbulbE14WSglobeopal470lm": "LED2002G5",
        "TRADFRIbulbE27WSglobeopal1055lm": "LED2003G10",
        "TRADFRIbulbGU10WS345lm": "LED2005R5",
        "TRADFRI bulb GU10 WW 345lm": "LED2005R5",
        "LEPTITER Recessed spot light": "T1820",
    },
    "Müller Licht": {
        "45327": "45318",
    },
    "Signify Netherlands B.V.": {
        "9290022166": "LCA001",
        "929003053401": "LCA001",
        "9290024687": "LCA007",
        "929002471601": "LCA008",
        "929001953101": "LCG002",
        "1741430P7": "LCS001",
        "1741530P7": "LCS001",
        "9290012573A": "LCT015",
        "440400982841": "LCT024",
        "7602031P7": "LCT026",
        "9290022169": "LTA001",
        "9290024719": "LTA011",
        "3261030P6": "LTC001",
        "3261031P6": "LTC001",
        "3261048P6": "LTC001",
        "3418931P6": "LTC012",
        "3417711P6": "LTW017",
        "8718699673147": "LWA001",
        "9290022268": "LWA003",
        "9290023351": "LWA008",
        "433714": "LWB004",
        "8718696449691": "LWB010",
        "9290022415": "LWO002",
        "9290024406": "LWU001",
        "9290011370B": "LWF001",
        "046677551780": "LWV002",
        "8719514328242": "LTA004",
        "8718699703424": "LCL001",
        "8718699671211": "LWE002",
        "9290020399": "LWE002",
        "915005106701": "LST002",
        "7299355PH": "LST001",
        # US Versions. Alias to EU versions
        "LCA005": "LCA001",
        "9290022266A": "LCA001",
    },
    "TuYa": {
        "TS0505B": "NO66-ZB/length_5",
    },
    "Yeelight": {
        "color2": "YLDP06YL",
        "ceiling10": "YLDL01YL",
        "mono1": "YLDP01YL",
        "strip6": "YLDD05YL",
        ### No profiles yet ###
        # "mono": "YLTD03YL",
        # "color6": "YLDP13AYL",
        # "colorb": "YLDP005",
        # "colorc": "YLDP004-A",
        # "RGBW": "MJDP02YL",
        # "lamp": "MJTD02YL",
        # "lamp1": "MJTD01YL",
        # "lamp15": "YLTD003",
        # "ceiling1": "YLXD01YL",
        # "ceiling2": "YLXD03YL",
        # "ceiling3": "YLXD05YL",
        # "ceiling4": "YLXD02YL",
        # "ceiling13": "YLXD01YL",
        # "ceil26": "YLXD76YL",
        # not a unique match, as it also may refer to "YLDP04YL"
        # "color4": "YLDP13YL",
        # not a unique match, as it also may refer to "YLDP03YL"
        # "color1": "YLDP02YL",
        # not a unique match, as it also may refer to "YLDD02YL"
        # "strip1": "YLDD01YL",
        # not a unique match, as it also may refer to "MJCTD02YL"
        # "bslamp1": "MJCTD01YL",
    },
}

custom_components/powercalc/__init__.py METASEP
"""The PowerCalc integration."""

from __future__ import annotations

import logging

import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.entity_registry as er
import voluptuous as vol
from awesomeversion.awesomeversion import AwesomeVersion
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.utility_meter import DEFAULT_OFFSET, max_28_days
from homeassistant.components.utility_meter.const import METER_TYPES
from homeassistant.const import (
    CONF_ENTITY_ID,
    CONF_PLATFORM,
    CONF_SCAN_INTERVAL,
    CONF_UNIQUE_ID,
    EVENT_HOMEASSISTANT_STARTED,
)
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.helpers import discovery
from homeassistant.helpers.typing import HomeAssistantType

from .common import create_source_entity, validate_name_pattern
from .const import (
    CONF_CREATE_DOMAIN_GROUPS,
    CONF_CREATE_ENERGY_SENSORS,
    CONF_CREATE_UTILITY_METERS,
    CONF_ENABLE_AUTODISCOVERY,
    CONF_ENERGY_INTEGRATION_METHOD,
    CONF_ENERGY_SENSOR_CATEGORY,
    CONF_ENERGY_SENSOR_FRIENDLY_NAMING,
    CONF_ENERGY_SENSOR_NAMING,
    CONF_ENERGY_SENSOR_PRECISION,
    CONF_POWER_SENSOR_CATEGORY,
    CONF_POWER_SENSOR_FRIENDLY_NAMING,
    CONF_POWER_SENSOR_NAMING,
    CONF_POWER_SENSOR_PRECISION,
    CONF_UTILITY_METER_OFFSET,
    CONF_UTILITY_METER_TARIFFS,
    CONF_UTILITY_METER_TYPES,
    DATA_CALCULATOR_FACTORY,
    DATA_CONFIGURED_ENTITIES,
    DATA_DISCOVERED_ENTITIES,
    DATA_DOMAIN_ENTITIES,
    DATA_USED_UNIQUE_IDS,
    DEFAULT_ENERGY_INTEGRATION_METHOD,
    DEFAULT_ENERGY_NAME_PATTERN,
    DEFAULT_ENERGY_SENSOR_PRECISION,
    DEFAULT_ENTITY_CATEGORY,
    DEFAULT_POWER_NAME_PATTERN,
    DEFAULT_POWER_SENSOR_PRECISION,
    DEFAULT_SCAN_INTERVAL,
    DEFAULT_UTILITY_METER_TYPES,
    DISCOVERY_LIGHT_MODEL,
    DISCOVERY_SOURCE_ENTITY,
    DOMAIN,
    DOMAIN_CONFIG,
    ENERGY_INTEGRATION_METHODS,
    ENTITY_CATEGORIES,
    MIN_HA_VERSION,
)
from .errors import ModelNotSupported
from .model_discovery import get_light_model, has_manufacturer_and_model_information
from .sensors.group import create_group_sensors
from .strategy.factory import PowerCalculatorStrategyFactory

CONFIG_SCHEMA = vol.Schema(
    {
        DOMAIN: vol.All(
            vol.Schema(
                {
                    vol.Optional(
                        CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
                    ): cv.time_period,
                    vol.Optional(
                        CONF_POWER_SENSOR_NAMING, default=DEFAULT_POWER_NAME_PATTERN
                    ): validate_name_pattern,
                    vol.Optional(
                        CONF_POWER_SENSOR_FRIENDLY_NAMING
                    ): validate_name_pattern,
                    vol.Optional(
                        CONF_POWER_SENSOR_CATEGORY, default=DEFAULT_ENTITY_CATEGORY
                    ): vol.In(ENTITY_CATEGORIES),
                    vol.Optional(
                        CONF_ENERGY_SENSOR_NAMING, default=DEFAULT_ENERGY_NAME_PATTERN
                    ): validate_name_pattern,
                    vol.Optional(
                        CONF_ENERGY_SENSOR_FRIENDLY_NAMING
                    ): validate_name_pattern,
                    vol.Optional(
                        CONF_ENERGY_SENSOR_CATEGORY, default=DEFAULT_ENTITY_CATEGORY
                    ): vol.In(ENTITY_CATEGORIES),
                    vol.Optional(CONF_ENABLE_AUTODISCOVERY, default=True): cv.boolean,
                    vol.Optional(CONF_CREATE_ENERGY_SENSORS, default=True): cv.boolean,
                    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): cv.boolean,
                    vol.Optional(CONF_UTILITY_METER_TARIFFS, default=[]): vol.All(
                        cv.ensure_list, [cv.string]
                    ),
                    vol.Optional(
                        CONF_UTILITY_METER_TYPES, default=DEFAULT_UTILITY_METER_TYPES
                    ): vol.All(cv.ensure_list, [vol.In(METER_TYPES)]),
                    vol.Optional(
                        CONF_UTILITY_METER_OFFSET, default=DEFAULT_OFFSET
                    ): vol.All(cv.time_period, cv.positive_timedelta, max_28_days),
                    vol.Optional(
                        CONF_ENERGY_INTEGRATION_METHOD,
                        default=DEFAULT_ENERGY_INTEGRATION_METHOD,
                    ): vol.In(ENERGY_INTEGRATION_METHODS),
                    vol.Optional(
                        CONF_ENERGY_SENSOR_PRECISION,
                        default=DEFAULT_ENERGY_SENSOR_PRECISION,
                    ): cv.positive_int,
                    vol.Optional(
                        CONF_POWER_SENSOR_PRECISION,
                        default=DEFAULT_POWER_SENSOR_PRECISION,
                    ): cv.positive_int,
                    vol.Optional(CONF_CREATE_DOMAIN_GROUPS, default=[]): vol.All(
                        cv.ensure_list, [cv.string]
                    ),
                }
            ),
        )
    },
    extra=vol.ALLOW_EXTRA,
)

_LOGGER = logging.getLogger(__name__)


async def async_setup(hass: HomeAssistantType, config: dict) -> bool:
    if AwesomeVersion(HA_VERSION) < AwesomeVersion(MIN_HA_VERSION):
        _LOGGER.critical(
            "Your HA version is outdated for this version of powercalc. Minimum required HA version is %s",
            MIN_HA_VERSION,
        )
        return False

    domain_config = config.get(DOMAIN) or {
        CONF_POWER_SENSOR_NAMING: DEFAULT_POWER_NAME_PATTERN,
        CONF_POWER_SENSOR_PRECISION: DEFAULT_POWER_SENSOR_PRECISION,
        CONF_POWER_SENSOR_CATEGORY: DEFAULT_ENTITY_CATEGORY,
        CONF_ENERGY_INTEGRATION_METHOD: DEFAULT_ENERGY_INTEGRATION_METHOD,
        CONF_ENERGY_SENSOR_NAMING: DEFAULT_ENERGY_NAME_PATTERN,
        CONF_ENERGY_SENSOR_PRECISION: DEFAULT_ENERGY_SENSOR_PRECISION,
        CONF_ENERGY_SENSOR_CATEGORY: DEFAULT_ENTITY_CATEGORY,
        CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
        CONF_CREATE_DOMAIN_GROUPS: [],
        CONF_CREATE_ENERGY_SENSORS: True,
        CONF_CREATE_UTILITY_METERS: False,
        CONF_ENABLE_AUTODISCOVERY: True,
        CONF_UTILITY_METER_OFFSET: DEFAULT_OFFSET,
        CONF_UTILITY_METER_TYPES: DEFAULT_UTILITY_METER_TYPES,
    }

    hass.data[DOMAIN] = {
        DATA_CALCULATOR_FACTORY: PowerCalculatorStrategyFactory(hass),
        DOMAIN_CONFIG: domain_config,
        DATA_CONFIGURED_ENTITIES: {},
        DATA_DOMAIN_ENTITIES: {},
        DATA_DISCOVERED_ENTITIES: [],
        DATA_USED_UNIQUE_IDS: [],
    }

    await autodiscover_entities(config, domain_config, hass)

    if domain_config.get(CONF_CREATE_DOMAIN_GROUPS):

        async def _create_domain_groups(event: None):
            await create_domain_groups(
                hass,
                domain_config,
                domain_config.get(CONF_CREATE_DOMAIN_GROUPS),
            )

        hass.bus.async_listen_once(
            EVENT_HOMEASSISTANT_STARTED,
            _create_domain_groups,
        )

    return True


async def autodiscover_entities(
    config: dict, domain_config: dict, hass: HomeAssistantType
):
    """Discover entities supported for powercalc autoconfiguration in HA instance"""

    if not domain_config.get(CONF_ENABLE_AUTODISCOVERY):
        return

    _LOGGER.debug("Start auto discovering entities")
    entity_registry = er.async_get(hass)
    for entity_entry in list(entity_registry.entities.values()):
        if entity_entry.disabled:
            continue

        if not entity_entry.domain in (LIGHT_DOMAIN, SWITCH_DOMAIN):
            continue

        if not await has_manufacturer_and_model_information(hass, entity_entry):
            continue

        manual_configuration = get_manual_configuration(config, entity_entry.entity_id)

        source_entity = await create_source_entity(entity_entry.entity_id, hass)
        try:
            light_model = await get_light_model(hass, {}, source_entity.entity_entry)
            if light_model.is_additional_configuration_required:
                if not manual_configuration:
                    _LOGGER.warning(
                        f"{entity_entry.entity_id}: Model found in database, but needs additional manual configuration to be loaded"
                    )
                    continue
        except ModelNotSupported:
            _LOGGER.debug(
                "%s: Model not found in library, skipping auto configuration",
                entity_entry.entity_id,
            )
            continue

        if not light_model:
            continue

        if not light_model.is_entity_domain_supported(source_entity.domain):
            continue

        discovery_info = {
            CONF_ENTITY_ID: entity_entry.entity_id,
            DISCOVERY_SOURCE_ENTITY: source_entity,
            DISCOVERY_LIGHT_MODEL: light_model,
        }
        hass.async_create_task(
            discovery.async_load_platform(
                hass, SENSOR_DOMAIN, DOMAIN, discovery_info, config
            )
        )

    _LOGGER.debug("Done auto discovering entities")


def get_manual_configuration(config: dict, entity_id: str) -> dict | None:
    if not SENSOR_DOMAIN in config:
        return None
    sensor_config = config.get(SENSOR_DOMAIN)
    for item in sensor_config:
        if item.get(CONF_PLATFORM) == DOMAIN and item.get(CONF_ENTITY_ID) == entity_id:
            return item
    return None


async def create_domain_groups(
    hass: HomeAssistantType, global_config: dict, domains: list[str]
):
    """Create group sensors aggregating all power sensors from given domains"""
    sensor_component = hass.data[SENSOR_DOMAIN]
    sensor_config = global_config.copy()
    _LOGGER.debug(f"Setting up domain based group sensors..")
    for domain in domains:
        if not domain in hass.data[DOMAIN].get(DATA_DOMAIN_ENTITIES):
            _LOGGER.error(f"Cannot setup group for domain {domain}, no entities found")
            continue

        domain_entities = hass.data[DOMAIN].get(DATA_DOMAIN_ENTITIES)[domain]
        sensor_config[CONF_UNIQUE_ID] = f"powercalc_domaingroup_{domain}"
        group_name = f"All {domain}"

        entities = await create_group_sensors(
            group_name, sensor_config, domain_entities, hass
        )
        await sensor_component.async_add_entities(entities)
    return []

custom_components/powercalc/config_flow.py METASEP

"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
            vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))
        }
    )

def _build_strategy_config(strategy: str, source_entity_id: str, user_input: dict[str,str] = None) -> dict[str, Any]:
infile
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
inproject
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
            vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))
        }
    )

def _build_strategy_config(strategy: str, source_entity_id: str, user_input: dict[str,str] = None) -> dict[str, Any]:
    strategy_schema = _get_strategy_schema(strategy, source_entity_id)
    strategy_options = {}
    for key in strategy_schema.schema.keys():
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
            vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))
        }
    )

def _build_strategy_config(strategy: str, source_entity_id: str, user_input: dict[str,str] = None) -> dict[str, Any]:
    strategy_schema = _get_strategy_schema(strategy, source_entity_id)
    strategy_options = {}
    for key in strategy_schema.schema.keys():
        if user_input.get(key) is None:
            continue
        strategy_options[str(key)] = user_input.get(key)
    return strategy_options

def _build_daily_energy_config(user_input: dict[str,str] = None) -> dict[str, Any]:
    schema = SCHEMA_DAILY_ENERGY_OPTIONS
    config = {}
    for key in schema.schema.keys():
        if user_input.get(key) is None:
            continue
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
            vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))
        }
    )

def _build_strategy_config(strategy: str, source_entity_id: str, user_input: dict[str,str] = None) -> dict[str, Any]:
    strategy_schema = _get_strategy_schema(strategy, source_entity_id)
    strategy_options = {}
    for key in strategy_schema.schema.keys():
        if user_input.get(key) is None:
            continue
        strategy_options[str(key)] = user_input.get(key)
    return strategy_options

def _build_daily_energy_config(user_input: dict[str,str] = None) -> dict[str, Any]:
    schema = SCHEMA_DAILY_ENERGY_OPTIONS
    config = {}
    for key in schema.schema.keys():
        if user_input.get(key) is None:
            continue
        config[str(key)] = user_input.get(key)
    return config

def _validate_daily_energy_input(user_input: dict[str, str] = None) -> dict:
    if not user_input:
        return {}
    errors = {}

    if not CONF_VALUE in user_input and not CONF_VALUE_TEMPLATE in user_input:
        errors["base"] = "daily_energy_mandatory"
    
    return errors

def _fill_schema_defaults(data_schema: vol.Schema, options: dict[str, str]):
    # Make a copy of the schema with suggested values set to saved options
    schema = {}
    for key, val in data_schema.schema.items():
        new_key = key
        if key in options:
            if isinstance(key, vol.Marker):
                if isinstance(key, vol.Optional) and callable(key.default) and key.default():
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
            vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))
        }
    )

def _build_strategy_config(strategy: str, source_entity_id: str, user_input: dict[str,str] = None) -> dict[str, Any]:
    strategy_schema = _get_strategy_schema(strategy, source_entity_id)
    strategy_options = {}
    for key in strategy_schema.schema.keys():
        if user_input.get(key) is None:
            continue
        strategy_options[str(key)] = user_input.get(key)
    return strategy_options

def _build_daily_energy_config(user_input: dict[str,str] = None) -> dict[str, Any]:
    schema = SCHEMA_DAILY_ENERGY_OPTIONS
    config = {}
    for key in schema.schema.keys():
        if user_input.get(key) is None:
            continue
        config[str(key)] = user_input.get(key)
    return config

def _validate_daily_energy_input(user_input: dict[str, str] = None) -> dict:
    if not user_input:
        return {}
    errors = {}

    if not CONF_VALUE in user_input and not CONF_VALUE_TEMPLATE in user_input:
        errors["base"] = "daily_energy_mandatory"
    
    return errors

def _fill_schema_defaults(data_schema: vol.Schema, options: dict[str, str]):
    # Make a copy of the schema with suggested values set to saved options
    schema = {}
    for key, val in data_schema.schema.items():
        new_key = key
        if key in options:
            if isinstance(key, vol.Marker):
                if isinstance(key, vol.Optional) and callable(key.default) and key.default():
                    new_key = vol.Optional(key.schema, default=options.get(key))
                else:
                    new_key = copy.copy(key)
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
            vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))
        }
    )

def _build_strategy_config(strategy: str, source_entity_id: str, user_input: dict[str,str] = None) -> dict[str, Any]:
    strategy_schema = _get_strategy_schema(strategy, source_entity_id)
    strategy_options = {}
    for key in strategy_schema.schema.keys():
        if user_input.get(key) is None:
            continue
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
common
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
            vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))
        }
    )

def _build_strategy_config(strategy: str, source_entity_id: str, user_input: dict[str,str] = None) -> dict[str, Any]:
    strategy_schema = _get_strategy_schema(strategy, source_entity_id)
    strategy_options = {}
    for key in strategy_schema.schema.keys():
        if user_input.get(key) is None:
            continue
        strategy_options[str(key)] = user_input.get(key)
    return strategy_options

def _build_daily_energy_config(user_input: dict[str,str] = None) -> dict[str, Any]:
    schema = SCHEMA_DAILY_ENERGY_OPTIONS
    config = {}
    for key in schema.schema.keys():
        if user_input.get(key) is None:
            continue
        config[str(key)] = user_input.get(key)
    return config

non_informative
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)
non_informative
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
non_informative
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

random
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
random
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
            
            if user_input.get(CONF_MODE) == MODE_WLED:
                return await self.async_step_wled()
            
        return self.async_show_form(
            step_id="virtual_power",
            data_schema=SCHEMA_POWER,
            errors={},
        )
    
    async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = _validate_daily_energy_input(user_input)

        if user_input is not None and not errors:
            unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)
            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.selected_sensor_type = SensorType.DAILY_ENERGY
            self.name = user_input.get(CONF_NAME)

            self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})
            return self.create_config_entry()
            
        return self.async_show_form(
            step_id="daily_energy",
            data_schema=SCHEMA_DAILY_ENERGY,
            errors=errors,
        )
    
    async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_FIXED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="fixed",
            data_schema=SCHEMA_POWER_FIXED,
            errors=errors,
        )
    
    async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_LINEAR: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="linear",
            data_schema=_create_linear_schema(self.source_entity_id),
            errors=errors,
        )
    
    async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:
        errors = {}
        if user_input is not None:
            self.sensor_config.update({CONF_WLED: user_input})
            errors = await self.validate_strategy_config()
            if not errors:
                return self.create_config_entry()

        return self.async_show_form(
            step_id="wled",
            data_schema=SCHEMA_POWER_WLED,
            errors=errors,
        )
    
    async def validate_strategy_config(self) -> dict:
        strategy_name = self.sensor_config.get(CONF_MODE)
        strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)
        try:
            await strategy.validate_config()
        except StrategyConfigurationError as error:
            return {"base": error.get_config_flow_translate_key()}
        return {}
    
    @callback
    def create_config_entry(self) -> FlowResult:
        self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})
        if self.name:
            self.sensor_config.update({CONF_NAME: self.name})
        if self.source_entity_id:
            self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id})
        if self.unique_id:
            self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
        return self.async_create_entry(
            title=self.name, data=self.sensor_config
        )


class OptionsFlowHandler(OptionsFlow):
    """Handle an option flow for PowerCalc."""

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.current_config: dict = dict(config_entry.data)
        self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
        self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)
        self.source_entity: SourceEntity = None

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle options flow."""

        self.current_config = dict(self.config_entry.data)
        if self.source_entity_id:
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
        
        errors = {}
        if user_input is not None:
            errors = await self.save_options(user_input)
            if not errors:
                return self.async_create_entry(title="", data={})

        return self.async_show_form(
            step_id="init",
            data_schema=self.build_options_schema(),
            errors=errors,
        )

    async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
        """Save options, and return errors when validation fails"""
        if self.sensor_type == SensorType.DAILY_ENERGY:
            daily_energy_config = _build_daily_energy_config(user_input)
            self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})
        
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            self.current_config.update(
                {
                    CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),
                    CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),
                    CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)
                }
            )
            strategy = self.current_config.get(CONF_MODE)

            strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)
            
            self.current_config.update({strategy: strategy_options})
            strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)
            try:
                await strategy_object.validate_config()
            except StrategyConfigurationError as error:
                return {"base": error.get_config_flow_translate_key()}

        self.hass.config_entries.async_update_entry(
            self.config_entry,
            data=self.current_config
        )
        return {}
    
    def build_options_schema(self) -> vol.Schema:
        """Build the options schema. depending on the selected sensor type"""

        strategy_options = {}
        if self.sensor_type == SensorType.VIRTUAL_POWER:
            base_power_schema = SCHEMA_POWER_OPTIONS
            strategy: str = self.current_config.get(CONF_MODE)
            strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
            data_schema = base_power_schema.extend(strategy_schema.schema)
            strategy_options = self.current_config.get(strategy)

        if self.sensor_type == SensorType.DAILY_ENERGY:
            data_schema = SCHEMA_DAILY_ENERGY_OPTIONS
            strategy_options = self.current_config[CONF_DAILY_FIXED_ENERGY]
        
        data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)
        return data_schema
    
    def get_current_strategy_options(self, strategy: str) -> dict[str, Any]:
        strategy_options: dict[str, Any] = self.current_config.get(strategy)
        if strategy == MODE_LINEAR and CONF_CALIBRATE in strategy_options:
            calibrate_options: dict = strategy_options[CONF_CALIBRATE]
            strategy_options[CONF_CALIBRATE] = {int(key): value for (key, value) in calibrate_options.items()}
        return strategy_options

def _create_strategy_object(
    hass: HomeAssistant,
    strategy: str,
    config: dict,
    source_entity: SourceEntity
) -> PowerCalculationStrategyInterface:
    factory = PowerCalculatorStrategyFactory(hass)
    return factory.create(config, strategy, None, source_entity)

def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
    if strategy == MODE_FIXED:
        return SCHEMA_POWER_FIXED
    if strategy == MODE_LINEAR:
        return _create_linear_schema(source_entity_id)
    if strategy == MODE_WLED:
        return SCHEMA_POWER_WLED
    return SCHEMA_POWER_FIXED

def _create_linear_schema(source_entity_id: str) -> vol.Schema:
    return SCHEMA_POWER_LINEAR.extend(
        {
            vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))
        }
    )

def _build_strategy_config(strategy: str, source_entity_id: str, user_input: dict[str,str] = None) -> dict[str, Any]:
    strategy_schema = _get_strategy_schema(strategy, source_entity_id)
    strategy_options = {}
    for key in strategy_schema.schema.keys():
        if user_input.get(key) is None:
            continue
        strategy_options[str(key)] = user_input.get(key)
    return strategy_options

def _build_daily_energy_config(user_input: dict[str,str] = None) -> dict[str, Any]:
    schema = SCHEMA_DAILY_ENERGY_OPTIONS
    config = {}
    for key in schema.schema.keys():
        if user_input.get(key) is None:
random
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
    SensorType
)
from .common import SourceEntity, create_source_entity
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
from .strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
from .strategy.strategy_interface import PowerCalculationStrategyInterface
from .errors import StrategyConfigurationError

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPE_MENU = {
    SensorType.DAILY_ENERGY: "Daily energy",
    SensorType.VIRTUAL_POWER: "Virtual power",
    #SensorType.GROUP: "Group"
}

SCHEMA_DAILY_ENERGY_OPTIONS = vol.Schema(
    {
        vol.Optional(CONF_VALUE): selector.NumberSelector(selector.NumberSelectorConfig(min=1, mode=selector.NumberSelectorMode.BOX)),
        vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
        vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=ENERGY_KILO_WATT_HOUR): vol.In(
            [ENERGY_KILO_WATT_HOUR, POWER_WATT]
        ),
        vol.Optional(CONF_ON_TIME): selector.DurationSelector(selector.DurationSelectorConfig(enable_day=False)),
        #vol.Optional(CONF_START_TIME): selector.TimeSelector(),
        vol.Optional(
            CONF_UPDATE_FREQUENCY, default=DEFAULT_DAILY_UPDATE_FREQUENCY
        ): selector.NumberSelector(selector.NumberSelectorConfig(min=10, unit_of_measurement="sec", mode=selector.NumberSelectorMode.BOX)),
    }
)
SCHEMA_DAILY_ENERGY = vol.Schema(
    {
        vol.Required(CONF_NAME): str,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
    }
).extend(SCHEMA_DAILY_ENERGY_OPTIONS.schema)

SCHEMA_POWER_OPTIONS = vol.Schema({
    vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float),
    vol.Optional(CONF_CREATE_ENERGY_SENSOR, default=True): selector.BooleanSelector(),
    vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): selector.BooleanSelector()
})

SCHEMA_POWER = vol.Schema({
    vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
    vol.Optional(CONF_NAME): selector.TextSelector(),
    vol.Optional(CONF_UNIQUE_ID): selector.TextSelector(),
    vol.Optional(CONF_MODE, default=MODE_FIXED): selector.SelectSelector(
        selector.SelectSelectorConfig(
            options=[MODE_FIXED, MODE_LINEAR, MODE_WLED],
            mode=selector.SelectSelectorMode.DROPDOWN,
        )
    ),
}).extend(SCHEMA_POWER_OPTIONS.schema)

SCHEMA_POWER_FIXED = vol.Schema({
    vol.Optional(CONF_POWER): vol.Coerce(float),
    vol.Optional(CONF_POWER_TEMPLATE): selector.TemplateSelector(),
    vol.Optional(CONF_STATES_POWER): selector.ObjectSelector()
})

SCHEMA_POWER_LINEAR = vol.Schema({
    vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
    vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
    vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
    vol.Optional(CONF_CALIBRATE): selector.ObjectSelector()
})

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PowerCalc."""

    VERSION = 1

    def __init__(self):
        """Initialize options flow."""
        self.sensor_config: dict[str, Any] = dict()
        self.selected_sensor_type: str = None
        self.name: str = None
        self.source_entity: SourceEntity = None
        self.source_entity_id: str = None
    
    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None) -> FlowResult:
        """Handle the initial step."""

        return self.async_show_menu(
            step_id="user", menu_options=SENSOR_TYPE_MENU
        )

    async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:
        if user_input is not None:
            self.source_entity_id = user_input[CONF_ENTITY_ID]
            self.source_entity = await create_source_entity(self.source_entity_id, self.hass)
            unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id

            await self.async_set_unique_id(unique_id)
            self._abort_if_unique_id_configured()

            self.name = user_input.get(CONF_NAME) or self.source_entity.name
            self.selected_sensor_type = SensorType.VIRTUAL_POWER
            self.sensor_config.update(user_input)

            if user_input.get(CONF_MODE) == MODE_FIXED:
                return await self.async_step_fixed()
            
            if user_input.get(CONF_MODE) == MODE_LINEAR:
                return await self.async_step_linear()
random
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
import logging
import copy
from select import select

import voluptuous as vol

from typing import Any
from homeassistant import config_entries
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import (
    CONF_ATTRIBUTE,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_UNIQUE_ID,
    CONF_UNIT_OF_MEASUREMENT,
    ENERGY_KILO_WATT_HOUR,
    POWER_WATT,
)
from homeassistant.helpers import selector
from homeassistant.config_entries import ConfigEntry, OptionsFlow
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult

from .const import (
    CONF_CREATE_UTILITY_METERS,
    CONF_DAILY_FIXED_ENERGY,
    CONF_FIXED,
    CONF_POWER_TEMPLATE,
    CONF_CALIBRATE,
    CONF_LINEAR,
    CONF_MIN_POWER,
    CONF_MAX_POWER,
    CONF_GAMMA_CURVE,
    CONF_SENSOR_TYPE,
    CONF_ON_TIME,
    CONF_START_TIME,
    CONF_STATES_POWER,
    CONF_VALUE_TEMPLATE,
    CONF_UPDATE_FREQUENCY,
    CONF_WLED,
    DOMAIN,
    CONF_MODE,
    CONF_VALUE,
    CONF_STANDBY_POWER,
    CALCULATION_MODES,
    MODE_FIXED,
    CONF_CREATE_ENERGY_SENSOR,
    CONF_POWER,
    MODE_LINEAR,
    MODE_WLED,
random
        return OptionsFlowHandler(config_entry)     async def async_step_user(self, user_input=None) -> FlowResult:     async def async_step_init(     async def async_step_virtual_power(self, user_input: dict[str,str] = None) -> FlowResult:             errors = await self.save_options(user_input)             data_schema=self.build_options_schema(),                 return await self.async_step_fixed()                 return await self.async_step_linear()     async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:                 return await self.async_step_wled()             daily_energy_config = _build_daily_energy_config(user_input)     async def async_step_daily_energy(self, user_input: dict[str,str] = None) -> FlowResult:         errors = _validate_daily_energy_input(user_input)             strategy_options = _build_strategy_config(strategy, self.source_entity_id, user_input)             self.sensor_config.update({CONF_DAILY_FIXED_ENERGY: _build_daily_energy_config(user_input)})             return self.create_config_entry()     async def async_step_fixed(self, user_input: dict[str,str] = None) -> FlowResult:             errors = await self.validate_strategy_config()             strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)     async def async_step_linear(self, user_input: dict[str,str] = None) -> FlowResult:         data_schema = _fill_schema_defaults(data_schema, self.current_config | strategy_options)             data_schema=_create_linear_schema(self.source_entity_id),     async def async_step_wled(self, user_input: dict[str,str] = None) -> FlowResult:         return _create_linear_schema(source_entity_id)     async def validate_strategy_config(self) -> dict:     strategy_schema = _get_strategy_schema(strategy, source_entity_id)             title=self.name, data=self.sensor_config         self.name: str = None         self.source_entity: SourceEntity = None             self.source_entity = await create_source_entity(self.source_entity_id, self.hass)             unique_id = user_input.get(CONF_UNIQUE_ID) or self.source_entity.unique_id or self.source_entity_id             await self.async_set_unique_id(unique_id)             self.name = user_input.get(CONF_NAME) or self.source_entity.name             unique_id = user_input.get(CONF_UNIQUE_ID) or user_input.get(CONF_NAME)             strategy_object = _create_strategy_object(self.hass, strategy, self.current_config, self.source_entity)             self.name = user_input.get(CONF_NAME)                 await strategy_object.validate_config()             except StrategyConfigurationError as error:         self.hass.config_entries.async_update_entry(     hass: HomeAssistant,     source_entity: SourceEntity ) -> PowerCalculationStrategyInterface:     factory = PowerCalculatorStrategyFactory(hass)     return factory.create(config, strategy, None, source_entity)         strategy = _create_strategy_object(self.hass, strategy_name, self.sensor_config, self.source_entity)             await strategy.validate_config()             vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(selector.AttributeSelectorConfig(entity_id=source_entity_id))         if self.name:             self.sensor_config.update({CONF_NAME: self.name})         if self.unique_id:             self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})         if user_input.get(key) is None:         config[str(key)] = user_input.get(key)         self.sensor_type: SensorType = self.current_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER         self.source_entity_id: str = self.current_config.get(CONF_ENTITY_ID)                     new_key = vol.Optional(key.schema, default=options.get(key))                     new_key.description = {"suggested_value": options.get(key)}             self.sensor_config.update(user_input)             if user_input.get(CONF_MODE) == MODE_FIXED:             if user_input.get(CONF_MODE) == MODE_LINEAR:             if user_input.get(CONF_MODE) == MODE_WLED:             self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})             self.current_config.update(                     CONF_CREATE_ENERGY_SENSOR: user_input.get(CONF_CREATE_ENERGY_SENSOR),                     CONF_CREATE_UTILITY_METERS: user_input.get(CONF_CREATE_UTILITY_METERS),                     CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER)             strategy = self.current_config.get(CONF_MODE)             self.current_config.update({strategy: strategy_options})             self.sensor_config.update({CONF_FIXED: user_input})             strategy: str = self.current_config.get(CONF_MODE)             strategy_options = self.current_config.get(strategy)             self.sensor_config.update({CONF_LINEAR: user_input})         strategy_options: dict[str, Any] = self.current_config.get(strategy)             self.sensor_config.update({CONF_WLED: user_input})         strategy_name = self.sensor_config.get(CONF_MODE)         self.sensor_config.update({CONF_SENSOR_TYPE: self.selected_sensor_type})         strategy_options[str(key)] = user_input.get(key)             self.sensor_config.update({CONF_ENTITY_ID: self.source_entity_id}) def _validate_daily_energy_input(user_input: dict[str, str] = None) -> dict:     {         if user_input is not None and not errors:         return data_schema             continue     SensorType