import type { FC } from 'react'
import type { VersionHistoryPanelProps } from '@/app/components/workflow/panel/version-history-panel'
import dynamic from 'next/dynamic'
import { memo, useCallback, useEffect, useRef } from 'react'
import { useStore as useReactflow } from 'reactflow'
import { useShallow } from 'zustand/react/shallow'
import { cn } from '@/utils/classnames'
import { Panel as NodePanel } from '../nodes'
import { useStore } from '../store'
import EnvPanel from './env-panel'

const VersionHistoryPanel = dynamic(() => import('@/app/components/workflow/panel/version-history-panel'), {
  ssr: false,
})

export type PanelProps = {
  components?: {
    left?: React.ReactNode
    right?: React.ReactNode
  }
  versionHistoryPanelProps?: VersionHistoryPanelProps
  withHeader?: boolean
}

/**
 * Reference MDN standard implementation：https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserverEntry/borderBoxSize
 */
const getEntryWidth = (entry: ResizeObserverEntry, element: HTMLElement): number => {
  if (entry.borderBoxSize?.length > 0)
    return entry.borderBoxSize[0].inlineSize

  if (entry.contentRect.width > 0)
    return entry.contentRect.width

  return element.getBoundingClientRect().width
}

const useResizeObserver = (
  callback: (width: number) => void,
  dependencies: React.DependencyList = [],
) => {
  const elementRef = useRef<HTMLDivElement>(null)

  const stableCallback = useCallback(callback, [callback])

  useEffect(() => {
    const element = elementRef.current
    if (!element)
      return

    const resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const width = getEntryWidth(entry, element)
        stableCallback(width)
      }
    })

    resizeObserver.observe(element)

    const initialWidth = element.getBoundingClientRect().width
    stableCallback(initialWidth)

    return () => {
      resizeObserver.disconnect()
    }
  }, [stableCallback, ...dependencies])
  return elementRef
}

const Panel: FC<PanelProps> = ({
  components,
  versionHistoryPanelProps,
  withHeader = true,
}) => {
  const selectedNode = useReactflow(useShallow((s) => {
    const nodes = s.getNodes()
    const currentNode = nodes.find(node => node.data.selected)

    if (currentNode) {
      return {
        id: currentNode.id,
        type: currentNode.type,
        data: currentNode.data,
      }
    }
  }))
  const showEnvPanel = useStore(s => s.showEnvPanel)
  const isRestoring = useStore(s => s.isRestoring)
  const showWorkflowVersionHistoryPanel = useStore(s => s.showWorkflowVersionHistoryPanel)

  // widths used for adaptive layout
  const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
  const previewPanelWidth = useStore(s => s.previewPanelWidth)
  const setPreviewPanelWidth = useStore(s => s.setPreviewPanelWidth)

  // When a node is selected and the NodePanel appears, if the current width
  // of preview/otherPanel is too large, it may result in the total width of
  // the two panels exceeding the workflowCanvasWidth, causing the NodePanel
  // to be pushed out. Here we check and, if necessary, reduce the previewPanelWidth
  // to "workflowCanvasWidth - 400 (minimum NodePanel width) - 400 (minimum canvas space)",
  // while still ensuring that previewPanelWidth ≥ 400.

  useEffect(() => {
    if (!selectedNode || !workflowCanvasWidth)
      return

    const reservedCanvasWidth = 400 // Reserve the minimum visible width for the canvas
    const minNodePanelWidth = 400
    const maxAllowed = Math.max(workflowCanvasWidth - reservedCanvasWidth - minNodePanelWidth, 400)

    if (previewPanelWidth > maxAllowed)
      setPreviewPanelWidth(maxAllowed)
  }, [selectedNode, workflowCanvasWidth, previewPanelWidth, setPreviewPanelWidth])

  const setRightPanelWidth = useStore(s => s.setRightPanelWidth)
  const setOtherPanelWidth = useStore(s => s.setOtherPanelWidth)

  const rightPanelRef = useResizeObserver(
    setRightPanelWidth,
    [setRightPanelWidth, selectedNode, showEnvPanel, showWorkflowVersionHistoryPanel],
  )

  const otherPanelRef = useResizeObserver(
    setOtherPanelWidth,
    [setOtherPanelWidth, showEnvPanel, showWorkflowVersionHistoryPanel],
  )

  return (
    <div
      ref={rightPanelRef}
      tabIndex={-1}
      className={cn(
        'absolute bottom-1 right-0 z-10 flex outline-none',
        withHeader ? 'top-14' : 'top-0',
      )}
      key={`${isRestoring}`}
    >
      {components?.left}
      {!!selectedNode && <NodePanel {...selectedNode} />}
      <div
        className="relative"
        ref={otherPanelRef}
      >
        {
          components?.right
        }
        {
          showWorkflowVersionHistoryPanel && (
            <VersionHistoryPanel {...versionHistoryPanelProps} />
          )
        }
        {
          showEnvPanel && (
            <EnvPanel />
          )
        }
      </div>
    </div>
  )
}

export default memo(Panel)
