import type { FC } from 'react'
import type { VersionHistoryPanelProps } from '@/app/components/workflow/panel/version-history-panel'
import { cn } from '@langgenius/dify-ui/cn'
import { memo, useEffect, useRef } from 'react'
import { useStore as useReactflow } from 'reactflow'
import { useShallow } from 'zustand/react/shallow'
import dynamic from '@/next/dynamic'
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
}

/**
 * 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) => {
  const elementRef = useRef<HTMLDivElement>(null)
  const widthRef = useRef<number | undefined>(undefined)
  const animationFrameRef = useRef<number | undefined>(undefined)

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

    widthRef.current = undefined

    const updateWidth = (width: number) => {
      if (widthRef.current === width)
        return

      widthRef.current = width
      if (animationFrameRef.current)
        cancelAnimationFrame(animationFrameRef.current)

      animationFrameRef.current = requestAnimationFrame(() => {
        animationFrameRef.current = undefined
        callback(width)
      })
    }

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

    resizeObserver.observe(element)

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

    return () => {
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current)
        animationFrameRef.current = undefined
      }
      resizeObserver.disconnect()
    }
  }, [callback])
  return elementRef
}

const Panel: FC<PanelProps> = ({
  components,
  versionHistoryPanelProps,
}) => {
  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)

  const otherPanelRef = useResizeObserver(setOtherPanelWidth)

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

export default memo(Panel)
