import type { Node } from '../../types'
import { resetReactFlowMockState, rfState } from '../../__tests__/reactflow-mock-state'
import { renderWorkflowHook } from '../../__tests__/workflow-test-env'
import { BlockEnum } from '../../types'
import { useHelpline } from '../use-helpline'

vi.mock('reactflow', async () =>
  (await import('../../__tests__/reactflow-mock-state')).createReactFlowModuleMock(),
)

function makeNode(overrides: Record<string, unknown> & { id: string }): Node {
  return {
    position: { x: 0, y: 0 },
    width: 240,
    height: 100,
    data: { type: BlockEnum.LLM, title: '', desc: '' },
    ...overrides,
  } as unknown as Node
}

describe('useHelpline', () => {
  beforeEach(() => {
    resetReactFlowMockState()
  })

  it('should return empty arrays for nodes in iteration', () => {
    rfState.nodes = [
      {
        id: 'n1',
        position: { x: 0, y: 0 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n2',
        position: { x: 0, y: 0 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({ id: 'n1', data: { type: BlockEnum.LLM, isInIteration: true } })
    const output = result.current.handleSetHelpline(draggingNode)

    expect(output.showHorizontalHelpLineNodes).toEqual([])
    expect(output.showVerticalHelpLineNodes).toEqual([])
  })

  it('should return empty arrays for nodes in loop', () => {
    rfState.nodes = [
      {
        id: 'n1',
        position: { x: 0, y: 0 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({ id: 'n1', data: { type: BlockEnum.LLM, isInLoop: true } })
    const output = result.current.handleSetHelpline(draggingNode)

    expect(output.showHorizontalHelpLineNodes).toEqual([])
    expect(output.showVerticalHelpLineNodes).toEqual([])
  })

  it('should detect horizontally aligned nodes (same y ±5px)', () => {
    rfState.nodes = [
      {
        id: 'n1',
        position: { x: 0, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n2',
        position: { x: 300, y: 103 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n3',
        position: { x: 600, y: 500 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({ id: 'n1', position: { x: 0, y: 100 } })
    const output = result.current.handleSetHelpline(draggingNode)

    const horizontalIds = output.showHorizontalHelpLineNodes.map((n: { id: string }) => n.id)
    expect(horizontalIds).toContain('n2')
    expect(horizontalIds).not.toContain('n3')
  })

  it('should detect vertically aligned nodes (same x ±5px)', () => {
    rfState.nodes = [
      {
        id: 'n1',
        position: { x: 100, y: 0 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n2',
        position: { x: 102, y: 200 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n3',
        position: { x: 500, y: 400 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({ id: 'n1', position: { x: 100, y: 0 } })
    const output = result.current.handleSetHelpline(draggingNode)

    const verticalIds = output.showVerticalHelpLineNodes.map((n: { id: string }) => n.id)
    expect(verticalIds).toContain('n2')
    expect(verticalIds).not.toContain('n3')
  })

  it('should apply entry node offset for Start nodes', () => {
    const ENTRY_OFFSET_Y = 21

    rfState.nodes = [
      {
        id: 'start',
        position: { x: 100, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.Start },
      },
      {
        id: 'n2',
        position: { x: 300, y: 100 + ENTRY_OFFSET_Y },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'far',
        position: { x: 300, y: 500 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({
      id: 'start',
      position: { x: 100, y: 100 },
      width: 240,
      height: 100,
      data: { type: BlockEnum.Start },
    })
    const output = result.current.handleSetHelpline(draggingNode)

    const horizontalIds = output.showHorizontalHelpLineNodes.map((n: { id: string }) => n.id)
    expect(horizontalIds).toContain('n2')
    expect(horizontalIds).not.toContain('far')
  })

  it('should apply entry node offset for Trigger nodes', () => {
    const ENTRY_OFFSET_Y = 21

    rfState.nodes = [
      {
        id: 'trigger',
        position: { x: 100, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.TriggerWebhook },
      },
      {
        id: 'n2',
        position: { x: 300, y: 100 + ENTRY_OFFSET_Y },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({
      id: 'trigger',
      position: { x: 100, y: 100 },
      width: 240,
      height: 100,
      data: { type: BlockEnum.TriggerWebhook },
    })
    const output = result.current.handleSetHelpline(draggingNode)

    const horizontalIds = output.showHorizontalHelpLineNodes.map((n: { id: string }) => n.id)
    expect(horizontalIds).toContain('n2')
  })

  it('should not detect alignment when positions differ by more than 5px', () => {
    rfState.nodes = [
      {
        id: 'n1',
        position: { x: 100, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n2',
        position: { x: 300, y: 106 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n3',
        position: { x: 106, y: 300 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({ id: 'n1', position: { x: 100, y: 100 } })
    const output = result.current.handleSetHelpline(draggingNode)

    expect(output.showHorizontalHelpLineNodes).toHaveLength(0)
    expect(output.showVerticalHelpLineNodes).toHaveLength(0)
  })

  it('should exclude child nodes in iteration', () => {
    rfState.nodes = [
      {
        id: 'n1',
        position: { x: 100, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'child',
        position: { x: 300, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM, isInIteration: true },
      },
    ]

    const { result } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({ id: 'n1', position: { x: 100, y: 100 } })
    const output = result.current.handleSetHelpline(draggingNode)

    const horizontalIds = output.showHorizontalHelpLineNodes.map((n: { id: string }) => n.id)
    expect(horizontalIds).not.toContain('child')
  })

  it('should set helpLineHorizontal in store when aligned nodes found', () => {
    rfState.nodes = [
      {
        id: 'n1',
        position: { x: 0, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n2',
        position: { x: 300, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result, store } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({ id: 'n1', position: { x: 0, y: 100 } })
    result.current.handleSetHelpline(draggingNode)

    expect(store.getState().helpLineHorizontal).toBeDefined()
  })

  it('should clear helpLineHorizontal when no aligned nodes', () => {
    rfState.nodes = [
      {
        id: 'n1',
        position: { x: 0, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'n2',
        position: { x: 300, y: 500 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result, store } = renderWorkflowHook(() => useHelpline())

    const draggingNode = makeNode({ id: 'n1', position: { x: 0, y: 100 } })
    result.current.handleSetHelpline(draggingNode)

    expect(store.getState().helpLineHorizontal).toBeUndefined()
  })

  it('should extend horizontal helpline when dragging node is before the first aligned node', () => {
    rfState.nodes = [
      {
        id: 'a',
        position: { x: 300, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'b',
        position: { x: 600, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result, store } = renderWorkflowHook(() => useHelpline())

    result.current.handleSetHelpline(makeNode({ id: 'dragging', position: { x: 100, y: 100 } }))

    expect(store.getState().helpLineHorizontal).toEqual({
      top: 100,
      left: 100,
      width: 440,
    })
  })

  it('should extend vertical helpline when dragging node is below the aligned nodes', () => {
    rfState.nodes = [
      {
        id: 'a',
        position: { x: 120, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
      {
        id: 'b',
        position: { x: 120, y: 260 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result, store } = renderWorkflowHook(() => useHelpline())

    result.current.handleSetHelpline(makeNode({ id: 'dragging', position: { x: 120, y: 420 } }))

    expect(store.getState().helpLineVertical).toEqual({
      top: 100,
      left: 120,
      height: 420,
    })
  })

  it('should extend horizontal helpline using entry node width when a start node is after the aligned nodes', () => {
    rfState.nodes = [
      {
        id: 'aligned',
        position: { x: 100, y: 100 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.LLM },
      },
    ]

    const { result, store } = renderWorkflowHook(() => useHelpline())

    result.current.handleSetHelpline(
      makeNode({
        id: 'start-node',
        position: { x: 500, y: 79 },
        width: 240,
        height: 100,
        data: { type: BlockEnum.Start },
      }),
    )

    expect(store.getState().helpLineHorizontal).toEqual({
      top: 100,
      left: 100,
      width: 640,
    })
  })
})
