// https://github.com/okonet/react-container-dimensions/commit/a45406e8f704a791718f4c38f568fb9f66966b59
// The MIT License (MIT)
//
// Copyright (c) 2016 Andrey Okonetchnikov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import { Component, createRef } from "react"
import { debounce } from "lodash"

interface ContainerDimensionsProps {
  children(
    props: ReturnType<typeof getDomNodeDimensions> & {
      ref: React.RefObject<HTMLElement>
    },
  ): React.ReactNode
  debounceTime?: number
}

function getDomNodeDimensions(node: Element) {
  const { top, right, bottom, left, width, height } = node.getBoundingClientRect()
  return { top, right, bottom, left, width, height }
}

export class ContainerDimensions extends Component<
  ContainerDimensionsProps,
  { initiated: false } | (DOMRect & { initiated: true })
> {
  #parentNode!: HTMLElement
  #elementResizeDetector!: ResizeObserver
  #componentIsMounted!: boolean
  #child = createRef<HTMLElement>()
  #onResizeDebounced?: () => void

  constructor(props: ContainerDimensionsProps) {
    super(props)
    this.state = {
      initiated: false,
    }
    this.onResize = this.onResize.bind(this)
    if (props.debounceTime != null) {
      this.#onResizeDebounced = debounce(this.onResize, props.debounceTime)
    }
  }

  override componentDidMount() {
    this.#parentNode = this.#child.current!.parentNode as HTMLElement
    this.#elementResizeDetector = new ResizeObserver(() => {
      if (this.props.debounceTime != null) {
        this.#onResizeDebounced!()
      } else {
        this.onResize()
      }
    })
    this.#elementResizeDetector.observe(this.#parentNode)

    this.#componentIsMounted = true
    this.onResize()
  }

  override componentWillUnmount() {
    this.#componentIsMounted = false
    this.#elementResizeDetector.unobserve(this.#parentNode)
  }

  onResize = () => {
    const clientRect = getDomNodeDimensions(this.#parentNode)
    if (this.#componentIsMounted) {
      this.setState({
        initiated: true,
        ...clientRect,
      })
    }
  }

  override render() {
    if (!this.state.initiated) {
      return <div ref={this.#child as React.RefObject<HTMLDivElement>} />
    }

    return this.props.children({ ...this.state, ref: this.#child })
  }
}
