import {
  mdiAccount,
  mdiAirFilter,
  mdiAlert,
  mdiAppleSafari,
  mdiBattery,
  mdiBell,
  mdiBookmark,
  mdiBrightness6,
  mdiBullhorn,
  mdiButtonPointer,
  mdiCalendar,
  mdiCalendarClock,
  mdiChatSleep,
  mdiClipboardList,
  mdiClock,
  mdiCodeBraces,
  mdiCog,
  mdiCommentAlert,
  mdiCounter,
  mdiDoorOpen,
  mdiEye,
  mdiFlash,
  mdiFlower,
  mdiFormatListBulleted,
  mdiFormTextbox,
  mdiForumOutline,
  mdiGarageOpen,
  mdiGate,
  mdiGoogleAssistant,
  mdiGoogleCirclesCommunities,
  mdiHomeAccount,
  mdiHomeAutomation,
  mdiImage,
  mdiImageFilterFrames,
  mdiLedOn,
  mdiLightbulb,
  mdiMapMarkerRadius,
  mdiMicrophoneMessage,
  mdiMotionSensor,
  mdiPalette,
  mdiRadioTower,
  mdiRayVertex,
  mdiRemote,
  mdiRobot,
  mdiRobotMower,
  mdiRobotVacuum,
  mdiRoomService,
  mdiScriptText,
  mdiSpeakerMessage,
  mdiStarFourPoints,
  mdiThermometer,
  mdiThermostat,
  mdiTimerOutline,
  mdiToggleSwitch,
  mdiWater,
  mdiWaterPercent,
  mdiWeatherPartlyCloudy,
  mdiWhiteBalanceSunny,
  mdiWindowClosed,
} from "@mdi/js";
import type { Connection, HassEntity } from "home-assistant-js-websocket";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { atLeastVersion } from "../common/config/version";
import { computeDomain } from "../common/entity/compute_domain";
import { computeObjectId } from "../common/entity/compute_object_id";
import { computeStateDomain } from "../common/entity/compute_state_domain";
import { stateIcon } from "../common/entity/state_icon";
import type { HomeAssistant } from "../types";
import type {
  EntityRegistryDisplayEntry,
  EntityRegistryEntry,
} from "./entity/entity_registry";

import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
import { callWS } from "../util/websocket";
import { getConditionDomain, getConditionObjectId } from "./condition";
import { getTriggerDomain, getTriggerObjectId } from "./trigger";

/** Icon to use when no icon specified for service. */
export const DEFAULT_SERVICE_ICON = mdiRoomService;

/** Icon to use when no icon specified for domain. */
export const DEFAULT_DOMAIN_ICON = mdiBookmark;

/** Fallback icons for each domain */
export const FALLBACK_DOMAIN_ICONS = {
  ai_task: mdiStarFourPoints,
  air_quality: mdiAirFilter,
  alert: mdiAlert,
  automation: mdiRobot,
  battery: mdiBattery,
  calendar: mdiCalendar,
  climate: mdiThermostat,
  configurator: mdiCog,
  conversation: mdiForumOutline,
  counter: mdiCounter,
  date: mdiCalendar,
  datetime: mdiCalendarClock,
  demo: mdiHomeAssistant,
  device_tracker: mdiAccount,
  door: mdiDoorOpen,
  garage_door: mdiGarageOpen,
  gate: mdiGate,
  google_assistant: mdiGoogleAssistant,
  group: mdiGoogleCirclesCommunities,
  homeassistant: mdiHomeAssistant,
  homekit: mdiHomeAutomation,
  humidity: mdiWaterPercent,
  illuminance: mdiBrightness6,
  image_processing: mdiImageFilterFrames,
  image: mdiImage,
  infrared: mdiLedOn,
  input_boolean: mdiToggleSwitch,
  input_button: mdiButtonPointer,
  input_datetime: mdiCalendarClock,
  input_number: mdiRayVertex,
  input_select: mdiFormatListBulleted,
  input_text: mdiFormTextbox,
  lawn_mower: mdiRobotMower,
  light: mdiLightbulb,
  moisture: mdiWater,
  motion: mdiMotionSensor,
  notify: mdiCommentAlert,
  number: mdiRayVertex,
  occupancy: mdiHomeAccount,
  persistent_notification: mdiBell,
  person: mdiAccount,
  plant: mdiFlower,
  power: mdiFlash,
  proximity: mdiAppleSafari,
  radio_frequency: mdiRadioTower,
  remote: mdiRemote,
  scene: mdiPalette,
  schedule: mdiCalendarClock,
  script: mdiScriptText,
  select: mdiFormatListBulleted,
  sensor: mdiEye,
  simple_alarm: mdiBell,
  siren: mdiBullhorn,
  stt: mdiMicrophoneMessage,
  sun: mdiWhiteBalanceSunny,
  temperature: mdiThermometer,
  text: mdiFormTextbox,
  time: mdiClock,
  timer: mdiTimerOutline,
  template: mdiCodeBraces,
  todo: mdiClipboardList,
  tts: mdiSpeakerMessage,
  vacuum: mdiRobotVacuum,
  wake_word: mdiChatSleep,
  weather: mdiWeatherPartlyCloudy,
  window: mdiWindowClosed,
  zone: mdiMapMarkerRadius,
};

const resources: {
  entity: Record<string, Promise<PlatformIcons>>;
  entity_component: {
    domains?: string[];
    resources?: Promise<Record<string, ComponentIcons>>;
  };
  services: {
    all?: Promise<Record<string, ServiceIcons>>;
    domains: Record<string, ServiceIcons | Promise<ServiceIcons>>;
  };
  triggers: {
    all?: Promise<Record<string, TriggerIcons>>;
    domains: Record<string, TriggerIcons | Promise<TriggerIcons>>;
  };
  conditions: {
    all?: Promise<Record<string, ConditionIcons>>;
    domains: Record<string, ConditionIcons | Promise<ConditionIcons>>;
  };
} = {
  entity: {},
  entity_component: {},
  services: { domains: {} },
  triggers: { domains: {} },
  conditions: { domains: {} },
};

interface IconResources<
  T extends
    | ComponentIcons
    | PlatformIcons
    | ServiceIcons
    | TriggerIcons
    | ConditionIcons,
> {
  resources: Record<string, T>;
}

type PlatformIcons = Record<
  string,
  {
    state: Record<string, string>;
    range?: Record<string, string>;
    state_attributes: Record<
      string,
      {
        state: Record<string, string>;
        range?: Record<string, string>;
        default: string;
      }
    >;
    default: string;
  }
>;

export type ComponentIcons = Record<
  string,
  {
    state?: Record<string, string>;
    range?: Record<string, string>;
    state_attributes?: Record<
      string,
      {
        state: Record<string, string>;
        range?: Record<string, string>;
        default: string;
      }
    >;
    default: string;
  }
>;

type ServiceIcons = Record<
  string,
  { service: string; sections?: Record<string, string> }
>;

type TriggerIcons = Record<
  string,
  { trigger: string; sections?: Record<string, string> }
>;

type ConditionIcons = Record<
  string,
  { condition: string; sections?: Record<string, string> }
>;

export type IconCategory =
  "entity" | "entity_component" | "services" | "triggers" | "conditions";

interface CategoryType {
  entity: PlatformIcons;
  entity_component: ComponentIcons;
  services: ServiceIcons;
  triggers: TriggerIcons;
  conditions: ConditionIcons;
}

export const getHassIcons = async <T extends IconCategory>(
  connection: Connection,
  category: T,
  integration?: string
) =>
  callWS<IconResources<CategoryType[T]>>(connection, {
    type: "frontend/get_icons",
    category,
    integration,
  });

export const getPlatformIcons = async (
  hassConfig: HomeAssistant["config"],
  connection: Connection,
  integration: string,
  force = false
): Promise<PlatformIcons | undefined> => {
  if (!force && integration in resources.entity) {
    return resources.entity[integration];
  }
  if (
    !isComponentLoaded(hassConfig, integration) ||
    !atLeastVersion(connection.haVersion, 2024, 2)
  ) {
    return undefined;
  }
  const result = getHassIcons(connection, "entity", integration).then(
    (res) => res?.resources[integration]
  );
  resources.entity[integration] = result;
  return resources.entity[integration];
};

export const getComponentIcons = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  domain: string,
  force = false
): Promise<ComponentIcons | undefined> => {
  // For Cast, old instances can connect to it.
  if (__BACKWARDS_COMPAT__ && !atLeastVersion(connection.haVersion, 2024, 2)) {
    return import("../fake_data/entity_component_icons")
      .then((mod) => mod.ENTITY_COMPONENT_ICONS)
      .then((res) => res[domain]);
  }

  if (
    !force &&
    resources.entity_component.resources &&
    resources.entity_component.domains?.includes(domain)
  ) {
    return resources.entity_component.resources.then((res) => res[domain]);
  }

  if (!isComponentLoaded(hassConfig, domain)) {
    return undefined;
  }
  resources.entity_component.domains = [...hassConfig.components];
  resources.entity_component.resources = getHassIcons(
    connection,
    "entity_component"
  ).then((result) => result.resources);
  return resources.entity_component.resources.then((res) => res[domain]);
};

export const getCategoryIcons = async <
  T extends Exclude<IconCategory, "entity" | "entity_component">,
>(
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  category: T,
  domain?: string,
  force = false
): Promise<CategoryType[T] | Record<string, CategoryType[T]> | undefined> => {
  const categoryResources = resources[category];
  if (!domain) {
    if (!force && categoryResources.all) {
      return categoryResources.all as Promise<Record<string, CategoryType[T]>>;
    }
    categoryResources.all = getHassIcons(connection, category).then((res) => {
      categoryResources.domains = res.resources as any;
      return res?.resources as Record<string, CategoryType[T]>;
    }) as any;
    return categoryResources.all as Promise<Record<string, CategoryType[T]>>;
  }
  if (!force && domain in categoryResources.domains) {
    return categoryResources.domains[domain] as Promise<CategoryType[T]>;
  }
  if (categoryResources.all && !force) {
    await categoryResources.all;
    if (domain in categoryResources.domains) {
      return categoryResources.domains[domain] as Promise<CategoryType[T]>;
    }
  }
  if (!isComponentLoaded(hassConfig, domain)) {
    return undefined;
  }
  const result = getHassIcons(connection, category, domain);
  categoryResources.domains[domain] = result.then(
    (res) => res?.resources[domain]
  ) as any;
  return categoryResources.domains[domain] as Promise<CategoryType[T]>;
};

export const getServiceIcons = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  domain?: string,
  force = false
): Promise<ServiceIcons | Record<string, ServiceIcons> | undefined> =>
  getCategoryIcons(connection, hassConfig, "services", domain, force);

export const getTriggerIcons = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  domain?: string,
  force = false
): Promise<TriggerIcons | Record<string, TriggerIcons> | undefined> =>
  getCategoryIcons(connection, hassConfig, "triggers", domain, force);

export const getConditionIcons = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  domain?: string,
  force = false
): Promise<ConditionIcons | Record<string, ConditionIcons> | undefined> =>
  getCategoryIcons(connection, hassConfig, "conditions", domain, force);

// Cache for sorted range keys
const sortedRangeCache = new WeakMap<Record<string, string>, number[]>();

// Helper function to get an icon from a range of values
const getIconFromRange = (
  value: number,
  range: Record<string, string>
): string | undefined => {
  // Get cached range values or compute and cache them
  let rangeValues = sortedRangeCache.get(range);
  if (!rangeValues) {
    rangeValues = Object.keys(range)
      .map(Number)
      .filter((k) => !isNaN(k))
      .sort((a, b) => a - b);
    sortedRangeCache.set(range, rangeValues);
  }

  if (rangeValues.length === 0) {
    return undefined;
  }

  // If the value is below the first threshold, return undefined
  // (we'll fall back to the default icon)
  if (value < rangeValues[0]) {
    return undefined;
  }

  // Find the highest threshold that's less than or equal to the value
  let selectedThreshold = rangeValues[0];
  for (const threshold of rangeValues) {
    if (value >= threshold) {
      selectedThreshold = threshold;
    } else {
      break;
    }
  }

  return range[selectedThreshold.toString()];
};

// Helper function to get an icon based on state and translations
const getIconFromTranslations = (
  state: string | number | undefined,
  translations:
    | {
        default?: string;
        state?: Record<string, string>;
        range?: Record<string, string>;
      }
    | undefined
): string | undefined => {
  if (!translations) {
    return undefined;
  }

  // First check for exact state match
  if (state && translations.state?.[state]) {
    return translations.state[state];
  }
  // Then check for range-based icons if we have a numeric state
  if (state !== undefined && translations.range && !isNaN(Number(state))) {
    return (
      getIconFromRange(Number(state), translations.range) ??
      translations.default
    );
  }
  // Fallback to default icon
  return translations.default;
};

export const entityIcon = async (
  entities: HomeAssistant["entities"],
  hassConfig: HomeAssistant["config"],
  hassConnection: Connection,
  stateObj: HassEntity,
  state?: string
) => {
  const entry = entities?.[stateObj.entity_id] as
    EntityRegistryDisplayEntry | undefined;
  if (entry?.icon) {
    return entry.icon;
  }
  const domain = computeStateDomain(stateObj);

  return getEntityIcon(
    hassConfig,
    hassConnection,
    domain,
    stateObj,
    state,
    entry
  );
};

export const entryIcon = async (
  hass: HomeAssistant,
  entry: EntityRegistryEntry | EntityRegistryDisplayEntry
) => {
  if (entry.icon) {
    return entry.icon;
  }
  const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
  const domain = computeDomain(entry.entity_id);
  return getEntityIcon(
    hass.config,
    hass.connection,
    domain,
    stateObj,
    undefined,
    entry
  );
};

const getEntityIcon = async (
  hassConfig: HomeAssistant["config"],
  hassConnection: Connection,
  domain: string,
  stateObj?: HassEntity,
  stateValue?: string,
  entry?: EntityRegistryEntry | EntityRegistryDisplayEntry
) => {
  const platform = entry?.platform;
  const translation_key = entry?.translation_key;
  const device_class = stateObj?.attributes.device_class;
  const state = stateValue ?? stateObj?.state;

  let icon: string | undefined;
  if (translation_key && platform) {
    const platformIcons = await getPlatformIcons(
      hassConfig,
      hassConnection,
      platform
    );
    if (platformIcons) {
      const translations = platformIcons[domain]?.[translation_key];

      icon = getIconFromTranslations(state, translations);
    }
  }

  if (!icon && stateObj) {
    icon = stateIcon(stateObj, state);
  }

  if (!icon) {
    const entityComponentIcons = await getComponentIcons(
      hassConnection,
      hassConfig,
      domain
    );
    if (entityComponentIcons) {
      const translations =
        (device_class && entityComponentIcons[device_class]) ||
        entityComponentIcons._;

      icon = getIconFromTranslations(state, translations);
    }
  }
  return icon;
};

export const attributeIcon = async (
  hassConfig: HomeAssistant["config"],
  hassConnection: HomeAssistant["connection"],
  entities: HomeAssistant["entities"],
  state: HassEntity,
  attribute: string,
  attributeValue?: string
) => {
  let icon: string | undefined;
  const domain = computeStateDomain(state);
  const deviceClass = state.attributes.device_class;
  const entity = entities[state.entity_id] as
    EntityRegistryDisplayEntry | undefined;
  const platform = entity?.platform;
  const translation_key = entity?.translation_key;
  const value =
    attributeValue ??
    (state.attributes[attribute] as string | number | undefined);

  if (translation_key && platform) {
    const platformIcons = await getPlatformIcons(
      hassConfig,
      hassConnection,
      platform
    );
    if (platformIcons) {
      icon = getIconFromTranslations(
        value,
        platformIcons[domain]?.[translation_key]?.state_attributes?.[attribute]
      );
    }
  }
  if (!icon) {
    const entityComponentIcons = await getComponentIcons(
      hassConnection,
      hassConfig,
      domain
    );
    if (entityComponentIcons) {
      const translations =
        (deviceClass &&
          entityComponentIcons[deviceClass]?.state_attributes?.[attribute]) ||
        entityComponentIcons._?.state_attributes?.[attribute];

      icon = getIconFromTranslations(value, translations);
    }
  }
  return icon;
};

export const triggerIcon = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  trigger: string
): Promise<string | undefined> => {
  let icon: string | undefined;

  const domain = getTriggerDomain(trigger);
  const triggerName = getTriggerObjectId(trigger);

  const triggerIcons = await getTriggerIcons(connection, hassConfig, domain);
  if (triggerIcons) {
    const trgrIcon = triggerIcons[triggerName] as TriggerIcons[string];
    icon = trgrIcon?.trigger;
  }
  if (!icon) {
    icon = await domainIcon(connection, hassConfig, domain);
  }
  return icon;
};

export const conditionIcon = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  condition: string
): Promise<string | undefined> => {
  let icon: string | undefined;

  const domain = getConditionDomain(condition);
  const conditionIcons = await getConditionIcons(
    connection,
    hassConfig,
    domain
  );
  if (conditionIcons) {
    const conditionName = getConditionObjectId(condition);
    const condIcon = conditionIcons[conditionName] as ConditionIcons[string];
    icon = condIcon?.condition;
  }
  if (!icon) {
    icon = await domainIcon(connection, hassConfig, domain);
  }
  return icon;
};

export const serviceIcon = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  service: string
): Promise<string | undefined> => {
  let icon: string | undefined;
  const domain = computeDomain(service);
  const serviceName = computeObjectId(service);
  const serviceIcons = await getServiceIcons(connection, hassConfig, domain);
  if (serviceIcons) {
    const srvceIcon = serviceIcons[serviceName] as ServiceIcons[string];
    icon = srvceIcon?.service;
  }
  if (!icon) {
    icon = await domainIcon(connection, hassConfig, domain);
  }
  return icon;
};

export const serviceSectionIcon = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  service: string,
  section: string
): Promise<string | undefined> => {
  const domain = computeDomain(service);
  const serviceName = computeObjectId(service);
  const serviceIcons = await getServiceIcons(connection, hassConfig, domain);
  if (serviceIcons) {
    const srvceIcon = serviceIcons[serviceName] as ServiceIcons[string];
    return srvceIcon?.sections?.[section];
  }
  return undefined;
};

export const domainIcon = async (
  connection: Connection,
  hassConfig: HomeAssistant["config"],
  domain: string,
  deviceClass?: string,
  state?: string
): Promise<string | undefined> => {
  const entityComponentIcons = await getComponentIcons(
    connection,
    hassConfig,
    domain
  );
  if (entityComponentIcons) {
    const translations =
      (deviceClass && entityComponentIcons[deviceClass]) ||
      entityComponentIcons._;
    // First check for exact state match
    if (state && translations.state?.[state]) {
      return translations.state[state];
    }
    // Then check for range-based icons if we have a numeric state
    if (state !== undefined && translations.range && !isNaN(Number(state))) {
      return (
        getIconFromRange(Number(state), translations.range) ??
        translations.default
      );
    }
    // Fallback to default icon
    return translations.default;
  }

  return undefined;
};
