import { HassConfig, HassEntity } from 'home-assistant-js-websocket';
import { computeDomain, isNumericFromAttributes } from '../helpers';
import { formatNumber, getNumberFormatOptions, blankBeforePercent } from './format_number';
import {
  LocalizeFunc,
  EntityRegistryDisplayEntry,
  FrontendLocaleData,
  HomeAssistant,
  TimeZone,
} from '../types/homeassistant';
import { UNIT_TO_MILLISECOND_CONVERT, formatDuration } from './duration';
import { formatDateTime } from './format_date_time';
import { formatDate } from './format_date';
import { formatTime } from './format_time';
import { UPDATE_SUPPORT_PROGRESS, updateIsInstallingFromAttributes } from './update';
import { supportsFeatureFromAttributes } from './supports-features';

const UNAVAILABLE = 'unavailable';
const UNKNOWN = 'unknown';

export const computeStateDisplaySingleEntity = (
  localize: LocalizeFunc,
  stateObj: HassEntity,
  locale: FrontendLocaleData,
  config: HassConfig,
  entity: EntityRegistryDisplayEntry | undefined,
  buttonConfig: { numeric_precision?: number; show_units?: boolean; units?: string } | undefined,
  state?: string,
): string =>
  computeStateDisplayFromEntityAttributes(
    localize,
    locale,
    config,
    entity,
    stateObj.entity_id,
    stateObj.attributes,
    buttonConfig,
    state !== undefined ? state : stateObj.state,
  );

export const computeStateDisplay = (
  localize: LocalizeFunc,
  stateObj: HassEntity,
  locale: FrontendLocaleData,
  config: HassConfig,
  entities: HomeAssistant['entities'],
  buttonConfig: { numeric_precision?: number; show_units?: boolean; units?: string } | undefined,
  state?: string,
): string => {
  const entity = entities[stateObj.entity_id] as EntityRegistryDisplayEntry | undefined;

  return computeStateDisplayFromEntityAttributes(
    localize,
    locale,
    config,
    entity,
    stateObj.entity_id,
    stateObj.attributes,
    buttonConfig,
    state !== undefined ? state : stateObj.state,
  );
};

export const computeStateDisplayFromEntityAttributes = (
  localize: LocalizeFunc,
  locale: FrontendLocaleData,
  config: HassConfig,
  entity: EntityRegistryDisplayEntry | undefined,
  entityId: string,
  attributes: any,
  buttonConfig: { numeric_precision?: number; show_units?: boolean; units?: string } | undefined,
  state: string,
): string => {
  if (state === UNKNOWN || state === UNAVAILABLE) {
    return localize(`state.default.${state}`);
  }

  // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
  if (isNumericFromAttributes(attributes)) {
    // state is duration
    if (
      attributes.device_class === 'duration' &&
      attributes.unit_of_measurement &&
      UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement]
    ) {
      try {
        return formatDuration(state, attributes.unit_of_measurement);
      } catch (_err) {
        // fallback to default
      }
    }
    if (attributes.device_class === 'monetary') {
      try {
        return formatNumber(state, locale, {
          style: 'currency',
          currency: buttonConfig?.units || attributes.unit_of_measurement,
          minimumFractionDigits: 2,
          // Override monetary options with number format
          ...getNumberFormatOptions({ state, attributes } as HassEntity, buttonConfig?.numeric_precision, entity),
        });
      } catch (_err) {
        // fallback to default
      }
    }
    const localOrConfigUnit = buttonConfig?.show_units
      ? buttonConfig?.units
        ? buttonConfig?.units
        : attributes.unit_of_measurement
      : undefined;
    const unit = !localOrConfigUnit
      ? ''
      : localOrConfigUnit === '%'
      ? blankBeforePercent(locale) + '%'
      : ` ${localOrConfigUnit}`;
    return `${formatNumber(
      state,
      locale,
      getNumberFormatOptions({ state, attributes } as HassEntity, buttonConfig?.numeric_precision, entity),
    )}${unit}`;
  }

  const domain = computeDomain(entityId);

  if (domain === 'datetime') {
    const time = new Date(state);
    return formatDateTime(time, locale, config);
  }

  if (['date', 'input_datetime', 'time'].includes(domain)) {
    // If trying to display an explicit state, need to parse the explicit state to `Date` then format.
    // Attributes aren't available, we have to use `state`.

    // These are timezone agnostic, so we should NOT use the system timezone here.
    try {
      const components = state.split(' ');
      if (components.length === 2) {
        // Date and time.
        return formatDateTime(new Date(components.join('T')), { ...locale, time_zone: TimeZone.local }, config);
      }
      if (components.length === 1) {
        if (state.includes('-')) {
          // Date only.
          return formatDate(new Date(`${state}T00:00`), { ...locale, time_zone: TimeZone.local }, config);
        }
        if (state.includes(':')) {
          // Time only.
          const now = new Date();
          return formatTime(
            new Date(`${now.toISOString().split('T')[0]}T${state}`),
            { ...locale, time_zone: TimeZone.local },
            config,
          );
        }
      }
      return state;
    } catch (_e) {
      // Formatting methods may throw error if date parsing doesn't go well,
      // just return the state string in that case.
      return state;
    }
  }

  // `counter` `number` and `input_number` domains do not have a unit of measurement but should still use `formatNumber`
  if (domain === 'counter' || domain === 'number' || domain === 'input_number') {
    // Format as an integer if the value and step are integers
    return formatNumber(
      state,
      locale,
      getNumberFormatOptions({ state, attributes } as HassEntity, buttonConfig?.numeric_precision, entity),
    );
  }

  // state is a timestamp
  if (
    ['button', 'event', 'input_button', 'scene', 'stt', 'tts'].includes(domain) ||
    (domain === 'sensor' && attributes.device_class === 'timestamp')
  ) {
    try {
      return formatDateTime(new Date(state), locale, config);
    } catch (_err) {
      return state;
    }
  }

  if (domain === 'update') {
    // When updating, and entity does not support % show "Installing"
    // When updating, and entity does support % show "Installing (xx%)"
    // When update available, show the version
    // When the latest version is skipped, show the latest version
    // When update is not available, show "Up-to-date"
    // When update is not available and there is no latest_version show "Unavailable"
    return state === 'on'
      ? updateIsInstallingFromAttributes(attributes)
        ? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS) &&
          typeof attributes.in_progress === 'number'
          ? localize('ui.card.update.installing_with_progress', {
              progress: attributes.in_progress,
            })
          : localize('ui.card.update.installing')
        : attributes.latest_version
      : attributes.skipped_version === attributes.latest_version
      ? attributes.latest_version ?? localize('state.default.unavailable')
      : localize('ui.card.update.up_to_date');
  }

  return (
    (entity?.translation_key &&
      localize(`component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}`)) ||
    // Return device class translation
    (attributes.device_class &&
      localize(`component.${domain}.entity_component.${attributes.device_class}.state.${state}`)) ||
    // Return default translation
    localize(`component.${domain}.entity_component._.state.${state}`) ||
    // We don't know! Return the raw state.
    state
  );
};
