import { useEffect, useRef, useState } from "preact/hooks";
import { HassEntity } from "home-assistant-js-websocket";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import relativeTime from "dayjs/plugin/relativeTime";

import { useHass } from "../../hooks";
import { TemperatureUnit } from "../../types";

// Load needed dayjs plugins
dayjs.extend(duration);
dayjs.extend(relativeTime);

// Time Constants
const S_IN_MIN = 60;
const S_IN_HR = 60 * 60;
const S_IN_DAY = 24 * 60 * 60;

/**
 * Mathematical functions to convert between temperature scales.
 * In the format of TEMPERATURE_CONVERSIONS[fromUnit][toUnit]
 * E.g. TEMPERATURE_CONVERSIONS["F"]["C"]
 *      |=> (t: number) => ((t - 32.0) * 5.0) / 9.0
 */
const TEMPERATURE_CONVERSIONS: any = {
  [TemperatureUnit.C]: {
    [TemperatureUnit.F]: (t: number) => (t * 9.0) / 5.0 + 32.0,
    [TemperatureUnit.K]: (t: number) => t + 273.15,
  },
  [TemperatureUnit.F]: {
    [TemperatureUnit.C]: (t: number) => ((t - 32.0) * 5.0) / 9.0,
    [TemperatureUnit.K]: (t: number) => ((t - 32.0) * 5.0) / 9.0 + 273.15,
  },
  /* finger lickin' good */
  [TemperatureUnit.K]: {
    [TemperatureUnit.F]: (t: number) => ((t - 273.15) * 9.0) / 5.0 + 32.0,
    [TemperatureUnit.C]: (t: number) => t - 273.15,
  },
};

/**
 * Renders a temperature string from a given (supported) Home Assistant Entity.
 * @param {HassEntity} entity The temperature entity to render.
 * @return {string} The formatted temperature string with respect to user's configuration.
 */
export const renderTemperature = (entity: HassEntity) => {
  const { config } = useHass();

  const rawTemperature = parseFloat(entity.state);
  const rawUnit = entity.attributes.unit_of_measurement?.substring(1);
  const desiredUnit = config.temperature_unit;

  if (
    desiredUnit === undefined ||
    desiredUnit === rawUnit ||
    !TEMPERATURE_CONVERSIONS[rawUnit!].hasOwnProperty(desiredUnit)
  )
    return `${
      config.round_temperature === true
        ? Math.round(rawTemperature)
        : rawTemperature
    }°${rawUnit}`;

  const convertedTemperature =
    TEMPERATURE_CONVERSIONS[rawUnit!][desiredUnit](rawTemperature);

  return `${
    config.round_temperature === true
      ? Math.round(convertedTemperature)
      : convertedTemperature.toFixed(2)
  }°${config.temperature_unit}`;
};

/**
 * Converts a given duration in seconds to a readable string.
 * @param {number} seconds The duration to format given in seconds.
 * @return {string} The formatted, readable duration string.
 */
const _formatDuration = (seconds: number) => {
  const { config } = useHass();

  // Use dayjs' `humanize` if we don't care about exact time
  if (!config.exact_time) {
    return dayjs
      .duration({
        seconds,
      })
      .humanize();
  }

  // Calculate each component of the time
  const days = Math.floor(seconds / S_IN_DAY);
  seconds %= S_IN_DAY;

  const hours = Math.floor(seconds / S_IN_HR);
  seconds %= S_IN_HR;

  const minutes = Math.floor(seconds / S_IN_MIN);
  seconds %= S_IN_MIN;

  // We conditionally render each part of the string based on whether or not they:
  // a) Are significant (>0)
  // b) Are sub-units of another part (e.g. if hours is 2, hours is rendered. If minutes is 0, we should still render it).
  const daysString = days > 0 ? `${days}d ` : "";
  const hoursString = hours > 0 || daysString.length > 0 ? `${hours}h ` : "";
  const minutesString =
    minutes > 0 || hoursString.length > 0 ? `${minutes}m ` : "";
  const secondsString =
    seconds > 0 || minutesString.length > 0 ? `${seconds}s` : "";

  return secondsString.length > 0
    ? `${daysString}${hoursString}${minutesString}${secondsString}`
    : "None";
};

/**
 * Renders a duration string from a given (supported) Home Assistant entity.
 * Updates every second.
 * @param {HassEntity} entity The duration entity to render.
 * @param {number} dir The "direction" to modify time in. (e.g., a `dir` of 1 will increment, -1 will decrement.
 * @return The formatted duration string with respect to the user's configuration.
 */
export const renderDuration = (entity: HassEntity, dir: number) => {
  try {
    const intervalId = useRef(-1);

    const initialSeconds = parseInt(entity.state);
    const [seconds, setSeconds] = useState(initialSeconds);

    const modTime = () => {
      setSeconds((s) => {
        const ns = s + dir;

        // If we're done counting down
        if (ns < 0 && dir < 0) {
          clearInterval(intervalId.current);
          return 0;
        }

        return ns;
      });
    };

    useEffect(() => {
      if (intervalId.current !== -1) clearInterval(intervalId.current);

      setInterval(modTime, 1000);

      return () => clearInterval(intervalId.current);
    }, []);

    useEffect(() => {
      try {
        setSeconds(parseInt(entity.state));
      } catch {}
    }, [entity]);

    return _formatDuration(seconds);
  } catch {
    return "Error!";
  }
};

/**
 * Renders an estimated time of arrival (ETA) string from a (supported) Home Assistant entity.
 * @param {HassEntity} entity The ETA entity to render.
 * @return The formatted ETA string with respect to the user's configuration.
 */
export const renderETA = (entity: HassEntity) => {
  const { config } = useHass();

  return dayjs()
    .add(parseInt(entity.state), "seconds")
    .format(config.use_24hr === true ? "HH:mm" : "h:mm a");
};

/**
 * Renders a percentage string from a (supported) Home Assistant entity.
 * @param {HassEntity} entity The percentage entity to render.
 * @return The formatted percentage string with respect to the user's configuration.
 */
export const renderPercentage = (entity: HassEntity) => {
  const { config } = useHass();

  const percentage = parseFloat(entity.state);

  return `${
    config.round_percentage === true ? Math.round(percentage) : percentage
  }%`;
};
