<commit_before>import React, { useState, useRef, useEffect } from 'react';
import { DimSocket, D2Item, PluggableInventoryItemDefinition } from 'app/inventory/item-types';
import Sheet from 'app/dim-ui/Sheet';
import { D2ManifestDefinitions } from 'app/destiny2/d2-definitions';
import {
  SocketPlugSources,
  TierType,
  DestinyInventoryItemDefinition,
  DestinyEnergyType,
  DestinyItemPlug,
  DestinyItemPlugBase,
} from 'bungie-api-ts/destiny2';
import BungieImage, { bungieNetPath } from 'app/dim-ui/BungieImage';
import { RootState } from 'app/store/reducers';
import { storesSelector, profileResponseSelector } from 'app/inventory/selectors';
import { connect } from 'react-redux';
import clsx from 'clsx';
import styles from './SocketDetails.m.scss';
import ElementIcon from 'app/inventory/ElementIcon';
import { compareBy, chainComparator, reverseComparator } from 'app/utils/comparators';
import { createSelector } from 'reselect';
import { itemsForPlugSet } from 'app/collections/plugset-helpers';
import _ from 'lodash';
import SocketDetailsSelectedPlug from './SocketDetailsSelectedPlug';
import { emptySet } from 'app/utils/empty';
import { getModCostInfo } from 'app/collections/Mod';
import { isPluggableItem } from 'app/inventory/store/sockets';

interface ProvidedProps {
  item: D2Item;
  socket: DimSocket;
  initialSelectedPlug?: DestinyInventoryItemDefinition;
  onClose(): void;
}

interface StoreProps {
  defs: D2ManifestDefinitions;
  inventoryPlugs: Set<number>;
  unlockedPlugs: Set<number>;
}

function mapStateToProps() {
  /** Build the hashes of all plug set item hashes that are unlocked by any character/profile. */
  const unlockedPlugsSelector = createSelector(
    profileResponseSelector,
    (_: RootState, props: ProvidedProps) =>
      props.socket.socketDefinition.reusablePlugSetHash ||
      props.socket.socketDefinition.randomizedPlugSetHash,
    (profileResponse, plugSetHash) => {
      if (!plugSetHash || !profileResponse) {
        return emptySet<number>();
      }
      const unlockedPlugs = new Set<number>();
      const plugSetItems = itemsForPlugSet(profileResponse, plugSetHash);
      for (const plugSetItem of plugSetItems) {
        if (plugSetItem.enabled) {
          unlockedPlugs.add(plugSetItem.plugItemHash);
        }
      }
      return unlockedPlugs;
    }
  );

  const inventoryPlugs = createSelector(
    storesSelector,
    (_: RootState, props: ProvidedProps) => props.socket,
    (state: RootState) => state.manifest.d2Manifest!,
    (stores, socket, defs) => {
      const socketType = defs.SocketType.get(socket.socketDefinition.socketTypeHash);
      if (
        !(
          socket.socketDefinition.plugSources & SocketPlugSources.InventorySourced &&
          socketType.plugWhitelist
        )
      ) {
        return emptySet<number>();
      }

      const modHashes = new Set<number>();

      const plugAllowList = new Set(socketType.plugWhitelist.map((e) => e.categoryHash));
      for (const store of stores) {
        for (const item of store.items) {
          const itemDef = defs.InventoryItem.get(item.hash);
          if (itemDef.plug && plugAllowList.has(itemDef.plug.plugCategoryHash)) {
            modHashes.add(item.hash);
          }
        }
      }

      return modHashes;
    }
  );

  return (state: RootState, props: ProvidedProps): StoreProps => ({
    defs: state.manifest.d2Manifest!,
    inventoryPlugs: inventoryPlugs(state, props),
    unlockedPlugs: unlockedPlugsSelector(state, props),
  });
}

type Props = ProvidedProps & StoreProps;

/**
 * This is needed because canInsert is false if an items socket already contains the plug. In this
 * event insertFailIndexes will contain an index that comes from the Plug Definitions, indicating
 * that a similar mod is already inserted. Unfortunately these only have a message, which varies
 * based on region, and no hash or id.
 */
export function plugIsInsertable(plug: DestinyItemPlug | DestinyItemPlugBase) {
  return plug.canInsert || plug.insertFailIndexes.length;
}

function SocketDetails({
  defs,
  item,
  socket,
  initialSelectedPlug,
  unlockedPlugs,
  inventoryPlugs,
  onClose,
}: Props) {
  const initialPlug =
    (isPluggableItem(initialSelectedPlug) && initialSelectedPlug) || socket.plugged?.plugDef;
  const [selectedPlug, setSelectedPlug] = useState<PluggableInventoryItemDefinition | null>(
    initialPlug || null
  );

  const socketType = defs.SocketType.get(socket.socketDefinition.socketTypeHash);
  const socketCategory = defs.SocketCategory.get(socketType.socketCategoryHash);

  // Start with the inventory plugs
  const modHashes = new Set<number>(inventoryPlugs);
  const otherUnlockedPlugs = new Set<number>();
  for (const modHash of inventoryPlugs) {
    otherUnlockedPlugs.add(modHash);
  }

  if (
    socket.socketDefinition.plugSources & SocketPlugSources.ReusablePlugItems &&
    socket.reusablePlugItems &&
    socket.reusablePlugItems.length
  ) {
    for (const plugItem of socket.reusablePlugItems) {
      modHashes.add(plugItem.plugItemHash);
      if (plugIsInsertable(plugItem)) {
        otherUnlockedPlugs.add(plugItem.plugItemHash);
      }
    }
  }

  if (socket.socketDefinition.reusablePlugSetHash) {
    for (const plugItem of defs.PlugSet.get(socket.socketDefinition.reusablePlugSetHash)
      .reusablePlugItems) {
      modHashes.add(plugItem.plugItemHash);
    }
  }
  if (socket.socketDefinition.randomizedPlugSetHash) {
    for (const plugItem of defs.PlugSet.get(socket.socketDefinition.randomizedPlugSetHash)
      .reusablePlugItems) {
      modHashes.add(plugItem.plugItemHash);
    }
  }

  const energyTypeHash = item.energy?.energyTypeHash;
  const energyType = energyTypeHash !== undefined && defs.EnergyType.get(energyTypeHash);

  let mods = Array.from(modHashes)
    .map((h) => defs.InventoryItem.get(h))
    .filter(
      (i) =>
        i.inventory!.tierType !== TierType.Common &&
        (!i.plug ||
          !i.plug.energyCost ||
          (energyType && i.plug.energyCost.energyTypeHash === energyType.hash) ||
          i.plug.energyCost.energyType === DestinyEnergyType.Any)
    )
    .filter(isPluggableItem)
    .sort(
      chainComparator(
        reverseComparator(
          compareBy((i) => unlockedPlugs.has(i.hash) || otherUnlockedPlugs.has(i.hash))
        ),
        compareBy((i) => i.plug?.energyCost?.energyCost),
        compareBy((i) => -i.inventory!.tierType),
        compareBy((i) => i.displayProperties.name)
      )
    );

  if (initialPlug) {
    mods = mods.filter((m) => m.hash !== initialPlug.hash);
    mods.unshift(initialPlug);
  }

  const requiresEnergy = mods.some((i) => i.plug?.energyCost?.energyCost);
  const initialItem = defs.InventoryItem.get(socket.socketDefinition.singleInitialItemHash);
  const header = (
    <h1>
      {initialItem && (<commit_msg>prevent failed lookup<commit_after>import React, { useState, useRef, useEffect } from 'react';
import { DimSocket, D2Item, PluggableInventoryItemDefinition } from 'app/inventory/item-types';
import Sheet from 'app/dim-ui/Sheet';
import { D2ManifestDefinitions } from 'app/destiny2/d2-definitions';
import {
  SocketPlugSources,
  TierType,
  DestinyInventoryItemDefinition,
  DestinyEnergyType,
  DestinyItemPlug,
  DestinyItemPlugBase,
} from 'bungie-api-ts/destiny2';
import BungieImage, { bungieNetPath } from 'app/dim-ui/BungieImage';
import { RootState } from 'app/store/reducers';
import { storesSelector, profileResponseSelector } from 'app/inventory/selectors';
import { connect } from 'react-redux';
import clsx from 'clsx';
import styles from './SocketDetails.m.scss';
import ElementIcon from 'app/inventory/ElementIcon';
import { compareBy, chainComparator, reverseComparator } from 'app/utils/comparators';
import { createSelector } from 'reselect';
import { itemsForPlugSet } from 'app/collections/plugset-helpers';
import _ from 'lodash';
import SocketDetailsSelectedPlug from './SocketDetailsSelectedPlug';
import { emptySet } from 'app/utils/empty';
import { getModCostInfo } from 'app/collections/Mod';
import { isPluggableItem } from 'app/inventory/store/sockets';

interface ProvidedProps {
  item: D2Item;
  socket: DimSocket;
  initialSelectedPlug?: DestinyInventoryItemDefinition;
  onClose(): void;
}

interface StoreProps {
  defs: D2ManifestDefinitions;
  inventoryPlugs: Set<number>;
  unlockedPlugs: Set<number>;
}

function mapStateToProps() {
  /** Build the hashes of all plug set item hashes that are unlocked by any character/profile. */
  const unlockedPlugsSelector = createSelector(
    profileResponseSelector,
    (_: RootState, props: ProvidedProps) =>
      props.socket.socketDefinition.reusablePlugSetHash ||
      props.socket.socketDefinition.randomizedPlugSetHash,
    (profileResponse, plugSetHash) => {
      if (!plugSetHash || !profileResponse) {
        return emptySet<number>();
      }
      const unlockedPlugs = new Set<number>();
      const plugSetItems = itemsForPlugSet(profileResponse, plugSetHash);
      for (const plugSetItem of plugSetItems) {
        if (plugSetItem.enabled) {
          unlockedPlugs.add(plugSetItem.plugItemHash);
        }
      }
      return unlockedPlugs;
    }
  );

  const inventoryPlugs = createSelector(
    storesSelector,
    (_: RootState, props: ProvidedProps) => props.socket,
    (state: RootState) => state.manifest.d2Manifest!,
    (stores, socket, defs) => {
      const socketType = defs.SocketType.get(socket.socketDefinition.socketTypeHash);
      if (
        !(
          socket.socketDefinition.plugSources & SocketPlugSources.InventorySourced &&
          socketType.plugWhitelist
        )
      ) {
        return emptySet<number>();
      }

      const modHashes = new Set<number>();

      const plugAllowList = new Set(socketType.plugWhitelist.map((e) => e.categoryHash));
      for (const store of stores) {
        for (const item of store.items) {
          const itemDef = defs.InventoryItem.get(item.hash);
          if (itemDef.plug && plugAllowList.has(itemDef.plug.plugCategoryHash)) {
            modHashes.add(item.hash);
          }
        }
      }

      return modHashes;
    }
  );

  return (state: RootState, props: ProvidedProps): StoreProps => ({
    defs: state.manifest.d2Manifest!,
    inventoryPlugs: inventoryPlugs(state, props),
    unlockedPlugs: unlockedPlugsSelector(state, props),
  });
}

type Props = ProvidedProps & StoreProps;

/**
 * This is needed because canInsert is false if an items socket already contains the plug. In this
 * event insertFailIndexes will contain an index that comes from the Plug Definitions, indicating
 * that a similar mod is already inserted. Unfortunately these only have a message, which varies
 * based on region, and no hash or id.
 */
export function plugIsInsertable(plug: DestinyItemPlug | DestinyItemPlugBase) {
  return plug.canInsert || plug.insertFailIndexes.length;
}

function SocketDetails({
  defs,
  item,
  socket,
  initialSelectedPlug,
  unlockedPlugs,
  inventoryPlugs,
  onClose,
}: Props) {
  const initialPlug =
    (isPluggableItem(initialSelectedPlug) && initialSelectedPlug) || socket.plugged?.plugDef;
  const [selectedPlug, setSelectedPlug] = useState<PluggableInventoryItemDefinition | null>(
    initialPlug || null
  );

  const socketType = defs.SocketType.get(socket.socketDefinition.socketTypeHash);
  const socketCategory = defs.SocketCategory.get(socketType.socketCategoryHash);

  // Start with the inventory plugs
  const modHashes = new Set<number>(inventoryPlugs);
  const otherUnlockedPlugs = new Set<number>();
  for (const modHash of inventoryPlugs) {
    otherUnlockedPlugs.add(modHash);
  }

  if (
    socket.socketDefinition.plugSources & SocketPlugSources.ReusablePlugItems &&
    socket.reusablePlugItems &&
    socket.reusablePlugItems.length
  ) {
    for (const plugItem of socket.reusablePlugItems) {
      modHashes.add(plugItem.plugItemHash);
      if (plugIsInsertable(plugItem)) {
        otherUnlockedPlugs.add(plugItem.plugItemHash);
      }
    }
  }

  if (socket.socketDefinition.reusablePlugSetHash) {
    for (const plugItem of defs.PlugSet.get(socket.socketDefinition.reusablePlugSetHash)
      .reusablePlugItems) {
      modHashes.add(plugItem.plugItemHash);
    }
  }
  if (socket.socketDefinition.randomizedPlugSetHash) {
    for (const plugItem of defs.PlugSet.get(socket.socketDefinition.randomizedPlugSetHash)
      .reusablePlugItems) {
      modHashes.add(plugItem.plugItemHash);
    }
  }

  const energyTypeHash = item.energy?.energyTypeHash;
  const energyType = energyTypeHash !== undefined && defs.EnergyType.get(energyTypeHash);

  let mods = Array.from(modHashes)
    .map((h) => defs.InventoryItem.get(h))
    .filter(
      (i) =>
        i.inventory!.tierType !== TierType.Common &&
        (!i.plug ||
          !i.plug.energyCost ||
          (energyType && i.plug.energyCost.energyTypeHash === energyType.hash) ||
          i.plug.energyCost.energyType === DestinyEnergyType.Any)
    )
    .filter(isPluggableItem)
    .sort(
      chainComparator(
        reverseComparator(
          compareBy((i) => unlockedPlugs.has(i.hash) || otherUnlockedPlugs.has(i.hash))
        ),
        compareBy((i) => i.plug?.energyCost?.energyCost),
        compareBy((i) => -i.inventory!.tierType),
        compareBy((i) => i.displayProperties.name)
      )
    );

  if (initialPlug) {
    mods = mods.filter((m) => m.hash !== initialPlug.hash);
    mods.unshift(initialPlug);
  }

  const requiresEnergy = mods.some((i) => i.plug?.energyCost?.energyCost);
  const initialItem =
    socket.socketDefinition.singleInitialItemHash &&
    defs.InventoryItem.get(socket.socketDefinition.singleInitialItemHash);
  const header = (
    <h1>
      {initialItem && (
{'fraction_non_alphanumeric': 0.11113535529856716, 'fraction_numerical': 0.0014546512473634446, 'mean_word_length': 3.7299621603027178, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5814551', 'n_tokens_mistral': 4285, 'n_tokens_neox': 4139, 'n_words': 855}