/* eslint-disable */

const DEBUG = false

// why-did-you-render/src/index.js
// @ts-expect-error no types
import react from "../../../node_modules/react/index.js"
import {
  defaults,
  get,
  groupBy,
  has,
  isError,
  isPlainObject,
  noop,
  sortBy,
  uniq,
} from "lodash"

const React = react as typeof import("react")
const { isValidElement, version } = React

type ExtendedReact = typeof import("react") & {
  isWDYR?: boolean
  __REVERT_WHY_DID_YOU_RENDER__?(): void
}

type WhyDidYouRenderComponentMember = WhyDidYouRenderOptions | boolean

declare module "react" {
  interface FunctionComponent<P = {}> {
    whyDidYouRender?: WhyDidYouRenderComponentMember
  }

  interface VoidFunctionComponent<P = {}> {
    whyDidYouRender?: WhyDidYouRenderComponentMember
  }

  interface ExoticComponent<P = {}> {
    whyDidYouRender?: WhyDidYouRenderComponentMember
  }

  namespace Component {
    const whyDidYouRender: WhyDidYouRenderComponentMember
  }
}

interface HookDifference {
  hookName?: string
  pathString: string
  diffType: string
  prevValue: unknown
  nextValue: unknown
}

interface UpdateReason {
  hookDifferences?: HookDifference[]
  propsDifferences?: HookDifference[]
  stateDifferences?: HookDifference[]
}

interface ReasonForUpdate extends UpdateReason {
  ownerDifferences?: UpdateReason
}

interface UpdateInfo {
  Component: React.Component
  displayName: string
  prevProps?: unknown
  prevState?: unknown
  nextProps?: unknown
  nextState?: unknown
  prevHook?: unknown
  nextHook?: unknown
  reason: ReasonForUpdate
  options?: WhyDidYouRenderOptions
  hookName?: string
}

type ExtraHookToTrack = [unknown, string]

interface WhyDidYouRenderOptions {
  include?: RegExp[]
  exclude?: RegExp[]
  trackAllPureComponents?: boolean
  trackHooks?: boolean
  logOwnerReasons?: boolean
  trackExtraHooks?: Array<ExtraHookToTrack>
  logOnDifferentValues?: boolean
  hotReloadBufferMs?: number
  onlyLogs?: boolean
  collapseGroups?: boolean
  titleColor?: string
  diffNameColor?: string
  diffPathColor?: string
  notifier?: Notifier
  customName?: string
}

type Notifier = (options: UpdateInfo) => void

interface OwnerData {
  Component: React.Component
  displayName: string
  props: unknown
  state: unknown
  hooks: unknown[]
  additionalOwnerData?: unknown
}

// why-did-you-render/src/wdyrStore.js
export const store: {
  /* The React object we patch */
  React: ExtendedReact
  /* Processed user options for WDYR */
  options: NormalizedOptions
  /* The original React.createElement function */
  origCreateElement: typeof React.createElement
  /* The original React.createFactory function */
  origCreateFactory: typeof React.createFactory
  /* The original React.cloneElement function */
  origCloneElement: typeof React.cloneElement
  /* A weak map of all React elements to their WDYR patched react elements */
  componentsMap: null | WeakMap<any, any>
  /* A weak map of props to the owner element that passed them */
  ownerDataMap: WeakMap<any, OwnerData>
  /* An array of hooks tracked during one render */
  hooksPerRender: {
    hook: any
  }[]
} = {
  React: undefined!,
  options: undefined!,
  origCreateElement: undefined!,
  origCreateFactory: undefined!,
  origCloneElement: undefined!,
  componentsMap: new WeakMap(),
  ownerDataMap: new WeakMap(),
  hooksPerRender: [],
}

// why-did-you-render/src/consts.js
enum diffTypes {
  different = "different",
  deepEquals = "deepEquals",
  date = "date",
  regex = "regex",
  reactElement = "reactElement",
  function = "function",
  same = "same",
}

const diffTypesDescriptions = {
  [diffTypes.different]: "different objects",
  [diffTypes.deepEquals]: "different objects that are equal by value",
  [diffTypes.date]: "different date objects with the same value",
  [diffTypes.regex]: "different regular expressions with the same value",
  [diffTypes.reactElement]:
    "different React elements (remember that the <jsx/> syntax always produces a *NEW* immutable React element so a component that receives <jsx/> as props always re-renders)",
  [diffTypes.function]: "different functions with the same name",
  [diffTypes.same]: "same objects by ref (===)",
}

const REACT_MEMO_TYPE = Symbol.for("react.memo")
const REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref")
const REACT_VERSION = Number(version.split(".")[0]) || 0
const REACT_STRICT_MODE = REACT_VERSION >= 18 ? 8 : 1

const hasElementType = typeof Element !== "undefined"

function trackDiff(
  a: unknown,
  b: unknown,
  diffsAccumulator: HookDifference[],
  pathString: string,
  diffType: diffTypes,
) {
  diffsAccumulator.push({
    diffType,
    pathString,
    prevValue: a,
    nextValue: b,
  })
  return diffType !== diffTypes.different
}

function isGetter(obj: any, prop: string) {
  return !!Object.getOwnPropertyDescriptor(obj, prop)?.get
}

const dependenciesMap = new WeakMap()

function accumulateDeepEqualDiffs(
  a: unknown,
  b: unknown,
  diffsAccumulator: HookDifference[],
  pathString = "",
  { detailed }: { detailed: boolean },
): boolean {
  if (a === b) {
    if (detailed) {
      trackDiff(a, b, diffsAccumulator, pathString, diffTypes.same)
    }
    return true
  }
  if (!a || !b) {
    return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different)
  }
  if (Array.isArray(a) && Array.isArray(b)) {
    const arrayLength = a.length
    if (arrayLength !== b.length) {
      return trackDiff([...a], [...b], diffsAccumulator, pathString, diffTypes.different)
    }
    const arrayItemDiffs: HookDifference[] = []
    let numberOfDeepEqualsItems = 0
    for (let i = arrayLength; i--; i > 0) {
      const diffEquals = accumulateDeepEqualDiffs(
        a[i],
        b[i],
        arrayItemDiffs,
        `${pathString}[${i}]`,
        { detailed },
      )
      if (diffEquals) {
        numberOfDeepEqualsItems++
      }
    }
    if (detailed || numberOfDeepEqualsItems !== arrayLength) {
      diffsAccumulator.push(...arrayItemDiffs)
    }
    if (numberOfDeepEqualsItems === arrayLength) {
      return trackDiff([...a], [...b], diffsAccumulator, pathString, diffTypes.deepEquals)
    }
    return trackDiff([...a], [...b], diffsAccumulator, pathString, diffTypes.different)
  }
  if (a instanceof Set && b instanceof Set) {
    if (a.size !== b.size) {
      return trackDiff(
        new Set(a),
        new Set(b),
        diffsAccumulator,
        pathString,
        diffTypes.different,
      )
    }
    for (const valA of a) {
      if (!b.has(valA)) {
        return trackDiff(
          new Set(a),
          new Set(b),
          diffsAccumulator,
          pathString,
          diffTypes.different,
        )
      }
    }
    return trackDiff(
      new Set(a),
      new Set(b),
      diffsAccumulator,
      pathString,
      diffTypes.deepEquals,
    )
  }
  if (a instanceof Date && b instanceof Date) {
    return a.getTime() === b.getTime()
      ? trackDiff(new Date(a), new Date(b), diffsAccumulator, pathString, diffTypes.date)
      : trackDiff(
          new Date(a),
          new Date(b),
          diffsAccumulator,
          pathString,
          diffTypes.different,
        )
  }
  if (a instanceof RegExp && b instanceof RegExp) {
    return a.toString() === b.toString()
      ? trackDiff(a, b, diffsAccumulator, pathString, diffTypes.regex)
      : trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different)
  }
  if (hasElementType && a instanceof Element && b instanceof Element) {
    return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different)
  }
  if (isValidElement(a) && isValidElement(b)) {
    if (a.type !== b.type) {
      return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different)
    }
    const reactElementPropsAreDeepEqual = accumulateDeepEqualDiffs(
      a.props,
      b.props,
      [],
      `${pathString}.props`,
      { detailed },
    )
    return reactElementPropsAreDeepEqual
      ? trackDiff(a, b, diffsAccumulator, pathString, diffTypes.reactElement)
      : trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different)
  }
  if (typeof a === "function" && typeof b === "function") {
    if (a.name !== b.name) {
      return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different)
    }
    const aDependenciesObj = dependenciesMap.get(a)
    const bDependenciesObj = dependenciesMap.get(b)
    if (aDependenciesObj && bDependenciesObj) {
      const dependenciesAreDeepEqual = accumulateDeepEqualDiffs(
        aDependenciesObj.deps,
        bDependenciesObj.deps,
        diffsAccumulator,
        `${pathString}:parent-hook-${aDependenciesObj.hookName}-deps`,
        { detailed },
      )
      return dependenciesAreDeepEqual
        ? trackDiff(a, b, diffsAccumulator, pathString, diffTypes.function)
        : trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different)
    }
    return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.function)
  }
  if (
    typeof a === "object" &&
    typeof b === "object" &&
    Object.getPrototypeOf(a) === Object.getPrototypeOf(b)
  ) {
    const aKeys = Object.getOwnPropertyNames(a)
    const bKeys = Object.getOwnPropertyNames(b)
    const allKeys = uniq([...aKeys, ...bKeys])
    const clonedA = isPlainObject(a) ? { ...a } : a
    const clonedB = isPlainObject(b) ? { ...b } : b
    if (allKeys.length !== aKeys.length || allKeys.length !== bKeys.length) {
      return trackDiff(
        clonedA,
        clonedB,
        diffsAccumulator,
        pathString,
        diffTypes.different,
      )
    }
    const relevantKeys = allKeys.filter(key => {
      if (key === "stack" && isError(a)) {
        return false
      }
      if (isGetter(a, key)) {
        return false
      }
      return true
    })
    const keysLength = relevantKeys.length
    for (let i = keysLength; i--; i > 0) {
      if (!has(b, relevantKeys[i])) {
        return trackDiff(
          clonedA,
          clonedB,
          diffsAccumulator,
          pathString,
          diffTypes.different,
        )
      }
    }
    const objectValuesDiffs: HookDifference[] = []
    let numberOfDeepEqualsObjectValues = 0
    for (let i = keysLength; i--; i > 0) {
      const key = relevantKeys[i]
      const deepEquals = accumulateDeepEqualDiffs(
        (a as Record<string, unknown>)[key],
        (b as Record<string, unknown>)[key],
        objectValuesDiffs,
        `${pathString}.${key}`,
        { detailed },
      )
      if (deepEquals) {
        numberOfDeepEqualsObjectValues++
      }
    }
    if (detailed || numberOfDeepEqualsObjectValues !== keysLength) {
      diffsAccumulator.push(...objectValuesDiffs)
    }
    if (numberOfDeepEqualsObjectValues === keysLength) {
      return trackDiff(
        clonedA,
        clonedB,
        diffsAccumulator,
        pathString,
        diffTypes.deepEquals,
      )
    }
    return trackDiff(clonedA, clonedB, diffsAccumulator, pathString, diffTypes.different)
  }
  return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different)
}

function calculateDeepEqualDiffs(
  a: unknown,
  b: unknown,
  initialPathString?: string,
  { detailed = false } = {},
) {
  try {
    const diffs: HookDifference[] = []
    accumulateDeepEqualDiffs(a, b, diffs, initialPathString, { detailed })
    return diffs
  } catch (error) {
    if (
      (error.message && error.message.match(/stack|recursion/i)) ||
      error.number === -2146828260
    ) {
      console.warn(
        "Warning: why-did-you-render couldn't handle circular references in props.",
        error.name,
        error.message,
      )
      return
    }
    throw error
  }
}

// why-did-you-render/src/printDiff.js
function printDiff(
  value1: unknown,
  value2: unknown,
  {
    pathString,
    consoleLog,
  }: {
    pathString: string
    consoleLog: typeof console.log
  },
) {
  const diffs = calculateDeepEqualDiffs(value1, value2, pathString, { detailed: true })!
  const keysLength = Math.max(...diffs.map(diff => diff.pathString.length)) + 2
  Object.entries(groupBy(sortBy(diffs, "pathString"), "diffType")).forEach(
    ([diffType, diffs2]) => {
      consoleLog(
        `%c${diffTypesDescriptions[diffType as diffTypes]}:`,
        "text-decoration: underline; color: blue;",
      )
      diffs2.forEach(diff => {
        consoleLog(`${diff.pathString}:`.padEnd(keysLength, " "), diff.prevValue)
      })
    },
  )
}

// why-did-you-render/src/defaultNotifier.js
const moreInfoUrl = "http://bit.ly/wdyr02"
const moreInfoHooksUrl = "http://bit.ly/wdyr3"
let inHotReload = false

function shouldLog(reason: ReasonForUpdate, Component: React.Component<{}, {}, any>) {
  if (inHotReload) {
    return false
  }
  if (store.options.logOnDifferentValues) {
    return true
  }
  if (Component.whyDidYouRender && Component.whyDidYouRender.logOnDifferentValues) {
    return true
  }
  const hasDifferentValues =
    reason.propsDifferences?.some(diff => diff.diffType === diffTypes.different) ||
    reason.stateDifferences?.some(diff => diff.diffType === diffTypes.different) ||
    reason.hookDifferences?.some(diff => diff.diffType === diffTypes.different)
  return !hasDifferentValues
}

function logDifference({
  Component,
  displayName,
  hookName,
  prefixMessage,
  diffObjType,
  differences,
  values,
}: {
  Component: React.Component
  displayName: string
  hookName?: string
  prefixMessage: string
  diffObjType: "props" | "state" | "hook"
  differences: HookDifference[]
  values: { prev: any; next: any }
}) {
  const { console } = store.options
  if (differences?.length) {
    console.log(
      { [displayName]: Component },
      `${prefixMessage} of ${diffObjType} changes:`,
    )
    for (const { pathString, diffType, prevValue, nextValue } of differences) {
      function diffFn() {
        printDiff(prevValue, nextValue, {
          pathString,
          consoleLog: console.log,
        })
      }
      console.group(
        `%c${
          diffObjType === "hook" ? `[hook ${hookName} result]` : `${diffObjType}.`
        }%c${pathString}%c`,
        `color:${store.options.diffNameColor};`,
        `color:${store.options.diffPathColor};`,
        "color:default;",
      )
      console.log(
        `${diffTypesDescriptions[diffType as diffTypes]}. (more info at ${
          hookName ? moreInfoHooksUrl : moreInfoUrl
        })`,
      )
      console.log({ [`prev ${pathString}`]: prevValue }, "!==", {
        [`next ${pathString}`]: nextValue,
      })
      if (diffType === diffTypes.deepEquals) {
        console.log({
          "For detailed diff, right click the following fn, save as global, and run: ":
            diffFn,
        })
      }
      console.groupEnd()
    }
  } else if (differences) {
    console.log(
      { [displayName]: Component },
      `${prefixMessage} the ${diffObjType} object itself changed but its values are all equal.`,
      diffObjType === "props"
        ? "This could have been avoided by making the component pure, or by preventing its father from re-rendering."
        : "This usually means this component called setState when no changes in its state actually occurred.",
      `More info at ${moreInfoUrl}`,
    )
    console.log(
      `prev ${diffObjType}:`,
      values.prev,
      " !== ",
      values.next,
      `:next ${diffObjType}`,
    )
  }
}

function defaultNotifier(updateInfo: UpdateInfo) {
  const {
    Component,
    displayName,
    hookName,
    prevProps,
    prevState,
    prevHook,
    nextProps,
    nextState,
    nextHook,
    reason,
  } = updateInfo
  if (!shouldLog(reason, Component)) {
    return
  }

  const { console } = store.options
  console.group(`%c${displayName}`, `color: ${store.options.titleColor};`)

  let prefixMessage = "Re-rendered because"
  if (reason.propsDifferences) {
    logDifference({
      Component,
      displayName,
      prefixMessage,
      diffObjType: "props",
      differences: reason.propsDifferences,
      values: { prev: prevProps, next: nextProps },
    })
    prefixMessage = "And because"
  }
  if (reason.stateDifferences) {
    logDifference({
      Component,
      displayName,
      prefixMessage,
      diffObjType: "state",
      differences: reason.stateDifferences,
      values: { prev: prevState, next: nextState },
    })
  }
  if (reason.hookDifferences) {
    logDifference({
      Component,
      displayName,
      prefixMessage,
      diffObjType: "hook",
      differences: reason.hookDifferences,
      values: { prev: prevHook, next: nextHook },
      hookName,
    })
  }
  if (reason.propsDifferences && reason.ownerDifferences) {
    const prevOwnerData = store.ownerDataMap.get(prevProps)!
    const nextOwnerData = store.ownerDataMap.get(nextProps)!
    console.group(`Rendered by ${nextOwnerData.displayName}`)
    let prefixMessage2 = "Re-rendered because"
    if (reason.ownerDifferences.propsDifferences) {
      logDifference({
        Component: nextOwnerData.Component,
        displayName: nextOwnerData.displayName,
        prefixMessage: prefixMessage2,
        diffObjType: "props",
        differences: reason.ownerDifferences.propsDifferences,
        values: { prev: prevOwnerData.props, next: nextOwnerData.props },
      })
      prefixMessage2 = "And because"
    }
    if (reason.ownerDifferences.stateDifferences) {
      logDifference({
        Component: nextOwnerData.Component,
        displayName: nextOwnerData.displayName,
        prefixMessage: prefixMessage2,
        diffObjType: "state",
        differences: reason.ownerDifferences.stateDifferences,
        values: { prev: prevOwnerData.state, next: nextOwnerData.state },
      })
    }
    if (reason.ownerDifferences.hookDifferences) {
      reason.ownerDifferences.hookDifferences.forEach(
        ({ hookName: hookName2, differences }, i) =>
          logDifference({
            Component: nextOwnerData.Component,
            displayName: nextOwnerData.displayName,
            prefixMessage: prefixMessage2,
            diffObjType: "hook",
            differences,
            values: {
              prev: prevOwnerData.hooks[i].result,
              next: nextOwnerData.hooks[i].result,
            },
            hookName: hookName2,
          }),
      )
    }
    console.groupEnd()
  }
  if (!reason.propsDifferences && !reason.stateDifferences && !reason.hookDifferences) {
    console.log(
      { [displayName]: Component },
      "Re-rendered although props and state objects are the same.",
      "This usually means there was a call to this.forceUpdate() inside the component.",
      `more info at ${moreInfoUrl}`,
    )
  }
  console.groupEnd()
}

function createDefaultNotifier(hotReloadBufferMs: number) {
  if (hotReloadBufferMs) {
    if (typeof module !== "undefined" && module.hot && module.hot.addStatusHandler) {
      module.hot.addStatusHandler(status => {
        if (status === "idle") {
          inHotReload = true
          setTimeout(() => {
            inHotReload = false
          }, hotReloadBufferMs)
        }
      })
    }
  }
  return defaultNotifier
}

// why-did-you-render/src/normalizeOptions.js
type NormalizedOptions = ReturnType<typeof normalizeOptions>

function normalizeOptions(userOptions: WhyDidYouRenderOptions = {}) {
  let consoleGroup = console.group
  let consoleGroupEnd = console.groupEnd
  if (userOptions.collapseGroups) {
    consoleGroup = console.groupCollapsed
  } else if (userOptions.onlyLogs) {
    consoleGroup = console.log
    consoleGroupEnd = noop
  }
  const notifier =
    userOptions.notifier ||
    createDefaultNotifier(
      "hotReloadBufferMs" in userOptions ? userOptions.hotReloadBufferMs : 500,
    )
  return {
    include: null,
    exclude: null,
    notifier,
    onlyLogs: false,
    console: {
      log: console.log,
      group: consoleGroup,
      groupEnd: consoleGroupEnd,
    },
    logOnDifferentValues: false,
    logOwnerReasons: true,
    trackHooks: true,
    titleColor: "#058",
    diffNameColor: "blue",
    diffPathColor: "red",
    trackExtraHooks: [],
    trackAllPureComponents: false,
    ...userOptions,
  }
}

// why-did-you-render/src/getDisplayName.js
function getDisplayName(type: React.ComponentType): string {
  return (
    type.displayName ||
    type.name ||
    ("type" in type && getDisplayName(type.type)) ||
    ("render" in type && getDisplayName(type.render)) ||
    (typeof type === "string" ? type : "Unknown")
  )
}

// why-did-you-render/src/getDefaultProps.js
function getDefaultProps(type: React.ComponentType): unknown {
  return (
    type.defaultProps ||
    ("type" in type && getDefaultProps(type.type)) ||
    ("render" in type && getDefaultProps(type.render)) ||
    {}
  )
}

// why-did-you-render/src/findObjectsDifferences.js
const emptyObject = {}

function findObjectsDifferences(
  userPrevObj: unknown,
  userNextObj: unknown,
  { shallow = true } = {},
) {
  if (userPrevObj === userNextObj) {
    return
  }
  if (!shallow) {
    return calculateDeepEqualDiffs(userPrevObj, userNextObj)
  }

  const prevObj = userPrevObj || emptyObject
  const nextObj = userNextObj || emptyObject
  const keysOfBothObjects = Object.keys({ ...prevObj, ...nextObj })
  return keysOfBothObjects.reduce((result, key) => {
    const deepEqualDiffs = calculateDeepEqualDiffs(prevObj[key], nextObj[key], key)
    if (deepEqualDiffs) {
      result = [...result, ...deepEqualDiffs]
    }
    return result
  }, [] as HookDifference[])
}

// why-did-you-render/src/getUpdateInfo.js
function getOwnerDifferences({
  prevOwnerData,
  nextOwnerData,
}: {
  prevOwnerData?: OwnerData
  nextOwnerData?: OwnerData
}) {
  if (!prevOwnerData || !nextOwnerData) {
    return
  }

  const prevOwnerDataHooks =
    prevOwnerData.hooks.length === nextOwnerData.hooks.length * 2
      ? prevOwnerData.hooks.slice(prevOwnerData.hooks.length / 2)
      : prevOwnerData.hooks

  const hookDifferences = prevOwnerDataHooks.map(({ hookName, result }, i) => ({
    hookName,
    differences: findObjectsDifferences(result, nextOwnerData.hooks[i].result, {
      shallow: false,
    }),
  }))

  return {
    propsDifferences: findObjectsDifferences(prevOwnerData.props, nextOwnerData.props),
    stateDifferences: findObjectsDifferences(prevOwnerData.state, nextOwnerData.state),
    hookDifferences: hookDifferences.length > 0 ? hookDifferences : undefined,
  }
}

function getUpdateReason(
  prevProps: unknown,
  prevState: unknown,
  prevHook: unknown,
  nextProps: unknown,
  nextState: unknown,
  nextHook: unknown,
): ReasonForUpdate {
  const prevOwnerData = store.ownerDataMap.get(prevProps)
  const nextOwnerData = store.ownerDataMap.get(nextProps)
  return {
    propsDifferences: findObjectsDifferences(prevProps, nextProps),
    stateDifferences: findObjectsDifferences(prevState, nextState),
    hookDifferences: findObjectsDifferences(prevHook, nextHook, { shallow: false }),
    ownerDifferences: getOwnerDifferences({ prevOwnerData, nextOwnerData }),
  }
}

function getUpdateInfo({
  Component,
  displayName,
  hookName,
  prevProps,
  prevState,
  prevHook,
  nextProps,
  nextState,
  nextHook,
}: Omit<UpdateInfo, "reason">): UpdateInfo {
  return {
    Component,
    displayName,
    hookName,
    prevProps,
    prevState,
    prevHook,
    nextProps,
    nextState,
    nextHook,
    reason: getUpdateReason(
      prevProps,
      prevState,
      prevHook,
      nextProps,
      nextState,
      nextHook,
    ),
  }
}

// why-did-you-render/src/utils.js
function checkIfInsideAStrictModeTree(
  reactComponentInstance: React.Component<unknown, unknown, unknown>,
) {
  let reactInternalFiber =
    reactComponentInstance &&
    (reactComponentInstance._reactInternalFiber || reactComponentInstance._reactInternals)
  while (reactInternalFiber) {
    if (reactInternalFiber.mode & REACT_STRICT_MODE) {
      return true
    }
    reactInternalFiber = reactInternalFiber.return
  }
  return false
}

function isReactClassComponent(Component: React.ComponentType) {
  return Component.prototype && !!Component.prototype.isReactComponent
}

function isMemoComponent(Component: React.ExoticComponent) {
  return Component.$$typeof === REACT_MEMO_TYPE
}

function isForwardRefComponent(Component: React.ExoticComponent) {
  return Component.$$typeof === REACT_FORWARD_REF_TYPE
}

// why-did-you-render/src/shouldTrack.js
function shouldInclude(displayName: string) {
  return (
    store.options.include &&
    store.options.include.length > 0 &&
    store.options.include.some(regex => regex.test(displayName))
  )
}

function shouldExclude(displayName: string) {
  return (
    store.options.exclude &&
    store.options.exclude.length > 0 &&
    store.options.exclude.some(regex => regex.test(displayName))
  )
}

function shouldTrack(Component, { isHookChange }) {
  const displayName = getDisplayName(Component)
  if (shouldExclude(displayName)) {
    return false
  }
  if (Component.whyDidYouRender === false) {
    return false
  }
  if (
    isHookChange &&
    Component.whyDidYouRender &&
    Component.whyDidYouRender.trackHooks === false
  ) {
    return false
  }
  return !!(
    Component.whyDidYouRender ||
    (store.options.trackAllPureComponents &&
      ((Component && Component.prototype instanceof store.React.PureComponent) ||
        isMemoComponent(Component))) ||
    shouldInclude(displayName)
  )
}

// why-did-you-render/src/patches/patchClassComponent.js
function patchClassComponent(ClassComponent, { displayName, defaultProps }) {
  class PatchedClassComponent extends ClassComponent {
    #state: {
      renderNumber: number
      isStrictMode?: boolean
      prevProps?: unknown
      prevState?: unknown
    }

    constructor(props: unknown, context: unknown) {
      super(props, context)
      this.#state = {
        renderNumber: 0,
      }
      const origRender = super.render || this.render
      const renderIsABindedFunction = origRender !== ClassComponent.prototype.render
      if (renderIsABindedFunction) {
        this.render = () => {
          PatchedClassComponent.prototype.render.apply(this)
          return origRender()
        }
      }
    }

    render() {
      this.#state.renderNumber++
      if (!("isStrictMode" in this.#state)) {
        this.#state.isStrictMode = checkIfInsideAStrictModeTree(this)
      }
      if (!(this.#state.isStrictMode && this.#state.renderNumber % 2 === 1)) {
        if (this.#state.prevProps) {
          const updateInfo = getUpdateInfo({
            Component: ClassComponent,
            displayName,
            prevProps: this.#state.prevProps,
            prevState: this.#state.prevState,
            nextProps: this.props,
            nextState: this.state,
          })
          store.options.notifier(updateInfo)
        }
        this.#state.prevProps = this.props
        this.#state.prevState = this.state
      }
      return super.render ? super.render() : null
    }
  }
  try {
    PatchedClassComponent.displayName = displayName
  } catch (e) {}
  PatchedClassComponent.defaultProps = defaultProps
  defaults(PatchedClassComponent, ClassComponent)
  return PatchedClassComponent
}

// why-did-you-render/src/patches/patchFunctionalOrStrComponent.js
var getFunctionalComponentFromStringComponent = componentTypeStr => props =>
  store.React.createElement(componentTypeStr, props)

function patchFunctionalOrStrComponent(
  FunctionalOrStringComponent,
  { isPure, displayName, defaultProps },
) {
  const FunctionalComponent =
    typeof FunctionalOrStringComponent === "string"
      ? getFunctionalComponentFromStringComponent(FunctionalOrStringComponent)
      : FunctionalOrStringComponent
  function WDYRFunctionalComponent() {
    const nextProps = arguments[0]
    const ref = store.React.useRef()
    const prevProps = ref.current
    ref.current = nextProps
    if (prevProps) {
      const updateInfo = getUpdateInfo({
        Component: FunctionalComponent,
        displayName,
        prevProps,
        nextProps,
      })
      const notifiedByHooks =
        !updateInfo.reason.propsDifferences ||
        (isPure && updateInfo.reason.propsDifferences.length === 0)
      if (!notifiedByHooks) {
        store.options.notifier(updateInfo)
      }
    }
    return FunctionalComponent(...arguments)
  }
  try {
    WDYRFunctionalComponent.displayName = displayName
  } catch (e) {}
  WDYRFunctionalComponent.defaultProps = defaultProps
  WDYRFunctionalComponent.ComponentForHooksTracking = FunctionalComponent
  defaults(WDYRFunctionalComponent, FunctionalComponent)
  return WDYRFunctionalComponent
}

// why-did-you-render/src/patches/patchMemoComponent.js
function patchMemoComponent(MemoComponent, { displayName, defaultProps }) {
  const { type: InnerMemoComponent } = MemoComponent
  const isInnerMemoComponentAClassComponent = isReactClassComponent(InnerMemoComponent)
  const isInnerMemoComponentForwardRefs = isForwardRefComponent(InnerMemoComponent)
  const isInnerMemoComponentAnotherMemoComponent = isMemoComponent(InnerMemoComponent)
  const WrappedFunctionalComponent = isInnerMemoComponentForwardRefs
    ? InnerMemoComponent.render
    : InnerMemoComponent
  const PatchedInnerComponent = isInnerMemoComponentAClassComponent
    ? patchClassComponent(WrappedFunctionalComponent, { displayName, defaultProps })
    : isInnerMemoComponentAnotherMemoComponent
    ? patchMemoComponent(WrappedFunctionalComponent, { displayName, defaultProps })
    : patchFunctionalOrStrComponent(WrappedFunctionalComponent, {
        displayName,
        isPure: true,
      })
  try {
    PatchedInnerComponent.displayName = getDisplayName(WrappedFunctionalComponent)
  } catch {}
  PatchedInnerComponent.ComponentForHooksTracking = MemoComponent
  defaults(PatchedInnerComponent, WrappedFunctionalComponent)
  const WDYRMemoizedFunctionalComponent = store.React.memo(
    isInnerMemoComponentForwardRefs
      ? store.React.forwardRef(PatchedInnerComponent)
      : PatchedInnerComponent,
    MemoComponent.compare,
  )
  try {
    WDYRMemoizedFunctionalComponent.displayName = displayName
  } catch (e) {}
  WDYRMemoizedFunctionalComponent.defaultProps = defaultProps
  defaults(WDYRMemoizedFunctionalComponent, MemoComponent)
  return WDYRMemoizedFunctionalComponent
}

// why-did-you-render/src/patches/patchForwardRefComponent.js
function patchForwardRefComponent(ForwardRefComponent, { displayName, defaultProps }) {
  const { render: InnerForwardRefComponent } = ForwardRefComponent
  const isInnerComponentMemoized = isMemoComponent(InnerForwardRefComponent)
  const WrappedFunctionalComponent = isInnerComponentMemoized
    ? InnerForwardRefComponent.type
    : InnerForwardRefComponent
  const WDYRWrappedByReactForwardRefFunctionalComponent = patchFunctionalOrStrComponent(
    WrappedFunctionalComponent,
    { isPure: isInnerComponentMemoized, displayName },
  )
  WDYRWrappedByReactForwardRefFunctionalComponent.displayName = getDisplayName(
    WrappedFunctionalComponent,
  )
  WDYRWrappedByReactForwardRefFunctionalComponent.ComponentForHooksTracking =
    WrappedFunctionalComponent
  defaults(WDYRWrappedByReactForwardRefFunctionalComponent, WrappedFunctionalComponent)
  const WDYRForwardRefFunctionalComponent = store.React.forwardRef(
    isInnerComponentMemoized
      ? store.React.memo(
          WDYRWrappedByReactForwardRefFunctionalComponent,
          InnerForwardRefComponent.compare,
        )
      : WDYRWrappedByReactForwardRefFunctionalComponent,
  )
  try {
    WDYRForwardRefFunctionalComponent.displayName = displayName
  } catch (e) {}
  WDYRForwardRefFunctionalComponent.defaultProps = defaultProps
  defaults(WDYRForwardRefFunctionalComponent, ForwardRefComponent)
  return WDYRForwardRefFunctionalComponent
}

// why-did-you-render/src/whyDidYouRender.js
var initialHookValue = Symbol("initial-hook-value")

function trackHookChanges(hookName, { path: hookPath }, hookResult) {
  const nextHook = hookPath ? get(hookResult, hookPath) : hookResult
  const renderNumberForTheHook = store.React.useRef(true)
  const isSecondCycleOfRenders =
    store.hooksPerRender[0] &&
    store.hooksPerRender[0].renderNumberForTheHook !== renderNumberForTheHook.current
  if (isSecondCycleOfRenders) {
    store.hooksPerRender = []
  }
  store.hooksPerRender.push({
    hookName,
    result: nextHook,
    renderNumberForTheHook: renderNumberForTheHook.current,
  })

  renderNumberForTheHook.current++

  const ComponentHookDispatchedFromInstance =
    store.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
    store.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner
      .current

  const prevHookRef = store.React.useRef(initialHookValue)
  if (!ComponentHookDispatchedFromInstance) {
    return hookResult
  }
  const Component =
    ComponentHookDispatchedFromInstance.type.ComponentForHooksTracking ||
    ComponentHookDispatchedFromInstance.type
  const displayName = getDisplayName(Component)
  const isShouldTrack = shouldTrack(Component, { isHookChange: true })
  if (!isShouldTrack) {
    return hookResult
  }
  const newPrevHookRef = prevHookRef.current
  prevHookRef.current = hookResult
  if (newPrevHookRef !== initialHookValue) {
    const notification = getUpdateInfo({
      Component,
      displayName,
      hookName,
      prevHook: hookPath ? get(newPrevHookRef, hookPath) : newPrevHookRef,
      nextHook,
    })
    if (notification.reason.hookDifferences) {
      store.options.notifier(notification)
    }
  }
  return hookResult
}

function createPatchedComponent(Component, { displayName, defaultProps }) {
  if (isMemoComponent(Component)) {
    return patchMemoComponent(Component, { displayName, defaultProps })
  }
  if (isForwardRefComponent(Component)) {
    return patchForwardRefComponent(Component, { displayName, defaultProps })
  }
  if (isReactClassComponent(Component)) {
    return patchClassComponent(Component, { displayName, defaultProps })
  }
  return patchFunctionalOrStrComponent(Component, {
    displayName,
    defaultProps,
    isPure: false,
  })
}

function getPatchedComponent(Component, { displayName, defaultProps }) {
  const componentsMap = store.componentsMap!
  if (componentsMap.has(Component)) {
    return componentsMap.get(Component)
  }
  const WDYRPatchedComponent = createPatchedComponent(Component, {
    displayName,
    defaultProps,
  })
  componentsMap.set(Component, WDYRPatchedComponent)
  return WDYRPatchedComponent
}

function getIsSupportedComponentType(Comp: React.ComponentType) {
  if (!Comp) {
    return false
  }
  if (isMemoComponent(Comp)) {
    return getIsSupportedComponentType(Comp.type)
  }
  if (isForwardRefComponent(Comp)) {
    return getIsSupportedComponentType(Comp.render)
  }
  if (typeof Comp === "function") {
    return true
  }
}

var hooksConfig = {
  useState: { path: "0" },
  useReducer: { path: "0" },
  useContext: void 0,
  useMemo: { dependenciesPath: "1", dontReport: true },
  useCallback: { dependenciesPath: "1", dontReport: true },
}
export function storeOwnerData(element) {
  const OwnerInstance =
    store.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner
      .current
  if (OwnerInstance) {
    const Component = OwnerInstance.type.ComponentForHooksTracking || OwnerInstance.type
    const displayName = getDisplayName(Component)
    let additionalOwnerData = {}
    if (store.options.getAdditionalOwnerData) {
      additionalOwnerData = store.options.getAdditionalOwnerData(element)
    }
    store.ownerDataMap.set(element.props, {
      Component,
      displayName,
      props: OwnerInstance.pendingProps,
      state: OwnerInstance.stateNode ? OwnerInstance.stateNode.state : null,
      hooks: store.hooksPerRender,
      additionalOwnerData,
    })
  }
}
function resetHooksPerRenderIfNeeded() {
  let currentOwner = null
  if (store.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) {
    Object.defineProperty(
      store.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,
      "current",
      {
        get() {
          return currentOwner
        },
        set(value) {
          currentOwner = value
          store.hooksPerRender = []
        },
      },
    )
  }
}
function trackHooksIfNeeded() {
  const hooksSupported = !!store.React.useState
  if (store.options.trackHooks && hooksSupported) {
    const nativeHooks = Object.entries(hooksConfig).map(
      ([hookName, hookTrackingConfig]) => {
        return [store.React, hookName, hookTrackingConfig]
      },
    )
    const hooksToTrack = [...nativeHooks, ...store.options.trackExtraHooks]
    hooksToTrack.forEach(([hookParent, hookName, hookTrackingConfig = {}]) => {
      const originalHook = hookParent[hookName]
      const newHookName = hookName[0].toUpperCase() + hookName.slice(1)
      const newHook = function (...args) {
        const hookResult = originalHook.call(this, ...args)
        const { dependenciesPath, dontReport } = hookTrackingConfig
        if (dependenciesPath && typeof hookResult === "function") {
          dependenciesMap.set(hookResult, {
            hookName,
            deps: get(args, dependenciesPath),
          })
        }
        if (!dontReport) {
          trackHookChanges(hookName, hookTrackingConfig, hookResult)
        }
        return hookResult
      }
      Object.defineProperty(newHook, "name", { value: newHookName, writable: false })
      Object.assign(newHook, { originalHook })
      hookParent[hookName] = newHook
    })
  }
}

export function getWDYRType(origType: React.ComponentType) {
  const isShouldTrack =
    getIsSupportedComponentType(origType) &&
    shouldTrack(origType, { isHookChange: false })
  if (!isShouldTrack) {
    return null
  }
  const displayName =
    (origType && origType.whyDidYouRender && origType.whyDidYouRender.customName) ||
    getDisplayName(origType)
  const defaultProps = getDefaultProps(origType)
  const WDYRPatchedComponent = getPatchedComponent(origType, {
    displayName,
    defaultProps,
  })
  return WDYRPatchedComponent
}

export default function whyDidYouRender(
  React: ExtendedReact,
  userOptions: WhyDidYouRenderOptions,
): ExtendedReact {
  if (React.isWDYR) {
    return React
  }

  store.React = React
  store.options = normalizeOptions(userOptions)
  store.origCreateElement = React.createElement
  store.origCreateFactory = React.createFactory
  store.origCloneElement = React.cloneElement
  store.componentsMap = new WeakMap()

  resetHooksPerRenderIfNeeded()
  React.createElement = function (origType: any, ...rest: any[]) {
    const WDYRType = getWDYRType(origType)
    if (WDYRType) {
      try {
        const element = store.origCreateElement.apply(React, [WDYRType, ...rest])
        if (store.options.logOwnerReasons) {
          storeOwnerData(element)
        }
        return element
      } catch (e) {
        store.options.console.log(
          "whyDidYouRender error. Please file a bug at https://github.com/welldone-software/why-did-you-render/issues.",
          {
            errorInfo: {
              error: e,
              componentNameOrComponent: origType,
              rest,
              options: store.options,
            },
          },
        )
      }
    }
    return store.origCreateElement.apply(React, [origType, ...rest])
  } as any

  Object.assign(React.createElement, store.origCreateElement)

  react.createFactory = (type: any) => {
    const factory = React.createElement.bind(null, type)
    factory.type = type
    return factory
  }

  Object.assign(React.createFactory, store.origCreateFactory)

  React.cloneElement = function (...args: any[]) {
    const element = store.origCloneElement.apply(React, args)
    if (store.options.logOwnerReasons) {
      storeOwnerData(element)
    }
    return element
  } as any

  Object.assign(React.cloneElement, store.origCloneElement)
  trackHooksIfNeeded()

  React.isWDYR = true
  React.__REVERT_WHY_DID_YOU_RENDER__ = () => {
    Object.assign(React, {
      createElement: store.origCreateElement,
      createFactory: store.origCreateFactory,
      cloneElement: store.origCloneElement,
    })
    store.componentsMap = null
    const hooksToRevert = [
      ...Object.keys(hooksConfig).map(hookName => [React, hookName] as const),
      ...store.options.trackExtraHooks,
    ]
    hooksToRevert.forEach(([hookParent, hookName]) => {
      if (hookParent[hookName].originalHook) {
        hookParent[hookName] = hookParent[hookName].originalHook
      }
    })
    delete React.__REVERT_WHY_DID_YOU_RENDER__
    delete React.isWDYR
  }

  return React
}

if (DEBUG) {
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  })
}

Object.assign(whyDidYouRender, React)
