import { test, expect } from './mind-elixir-test'

const data = {
  nodeData: {
    topic: 'Root Topic',
    id: 'root',
    children: [
      {
        id: 'left-main',
        topic: 'Left Main',
        children: [
          {
            id: 'left-child-1',
            topic: 'Left Child 1',
          },
          {
            id: 'left-child-2',
            topic: 'Left Child 2',
          },
          {
            id: 'left-child-3',
            topic: 'Left Child 3',
          },
        ],
      },
      {
        id: 'right-main',
        topic: 'Right Main',
        children: [
          {
            id: 'right-child-1',
            topic: 'Right Child 1',
          },
          {
            id: 'right-child-2',
            topic: 'Right Child 2',
          },
        ],
      },
    ],
  },
}

test.beforeEach(async ({ me }) => {
  await me.init(data)
})

test('Create arrow between two nodes', async ({ page, me }) => {
  // Get the MindElixir instance and create arrow programmatically
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')

    // Create arrow between two nodes
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Verify arrow SVG group appears
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify arrow path is visible
  await expect(page.locator('svg g[data-linkid] path').first()).toBeVisible()

  // Verify arrow head is visible
  await expect(page.locator('svg g[data-linkid] path').nth(1)).toBeVisible()

  // Verify arrow label is visible
  await expect(page.locator('svg g[data-linkid] text')).toBeVisible()
  await expect(page.locator('svg g[data-linkid] text')).toHaveText('Custom Link')
})

test('Create arrow with custom options', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')

    // Create arrow with custom style options
    instance.createArrow(leftChild1, rightChild1, {
      bidirectional: true,
      style: {
        stroke: '#ff0000',
        strokeWidth: '3',
        strokeDasharray: '5,5',
        labelColor: '#0000ff',
      },
    })
  }, instanceHandle)

  // Verify arrow appears
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify arrow appears with bidirectional option
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify multiple paths exist for bidirectional arrow (includes hotzone and highlight paths)
  const pathCount = await page.locator('svg g[data-linkid] path').count()
  expect(pathCount).toBeGreaterThan(3) // Should have more than 3 paths for bidirectional

  // Verify custom label color
  const arrowLabel = page.locator('svg g[data-linkid] text')
  await expect(arrowLabel).toHaveAttribute('fill', '#0000ff')
})

test('Create arrow from arrow object', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    // Create arrow from arrow object
    instance.createArrowFrom({
      label: 'Test Arrow',
      from: 'left-child-1',
      to: 'right-child-1',
      delta1: { x: 50, y: 20 },
      delta2: { x: -50, y: -20 },
      style: {
        stroke: '#00ff00',
        strokeWidth: '2',
      },
    })
  }, instanceHandle)

  // Verify arrow appears
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify custom label
  await expect(page.locator('svg g[data-linkid] text')).toHaveText('Test Arrow')

  // Verify arrow appears with custom properties
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify at least one path has the custom style (may be applied to different elements)
  const hasCustomStroke = await page.evaluate(() => {
    const paths = document.querySelectorAll('svg g[data-linkid] path')
    return Array.from(paths).some(path => path.getAttribute('stroke') === '#00ff00' || path.getAttribute('stroke-width') === '2')
  })
  expect(hasCustomStroke).toBe(true)
})

test('Select and highlight arrow', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow first
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Click on the arrow to select it
  await page.locator('svg g[data-linkid]').click()

  // Verify highlight appears (highlight group with higher opacity)
  await expect(page.locator('svg g[data-linkid] .arrow-highlight')).toBeVisible()

  // Verify control points appear (they are div elements with class 'circle')
  await expect(page.locator('.circle').first()).toBeVisible()
  await expect(page.locator('.circle').last()).toBeVisible()

  // Verify link controller appears
  await expect(page.locator('.linkcontroller')).toBeVisible()
})

test('Remove arrow', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow first
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Verify arrow exists
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Remove arrow programmatically
  await page.evaluate(async instance => {
    instance.removeArrow()
  }, instanceHandle)

  // Verify arrow is removed
  await expect(page.locator('svg g[data-linkid]')).not.toBeVisible()
})

test('Edit arrow label', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow first
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Double click on arrow label to edit
  await page.locator('svg g[data-linkid] text').dblclick()

  // Verify input box appears
  await expect(page.locator('#input-box')).toBeVisible()

  // Type new label
  await page.keyboard.press('Control+a')
  await page.keyboard.insertText('Updated Arrow Label')
  await page.keyboard.press('Enter')

  // Verify input box disappears
  await expect(page.locator('#input-box')).toBeHidden()

  // Verify new label is displayed
  await expect(page.locator('svg g[data-linkid] text')).toHaveText('Updated Arrow Label')
})

test('Unselect arrow', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow first
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Select arrow
  await page.locator('svg g[data-linkid]').click()
  await expect(page.locator('svg g[data-linkid] .arrow-highlight')).toBeVisible()

  // Unselect arrow programmatically
  await page.evaluate(async instance => {
    instance.unselectArrow()
  }, instanceHandle)

  // Verify highlight disappears
  await expect(page.locator('svg g[data-linkid] .arrow-highlight')).not.toBeVisible()

  // Verify control points disappear
  await expect(page.locator('.circle').first()).not.toBeVisible()
  await expect(page.locator('.circle').last()).not.toBeVisible()
})

test('Render multiple arrows', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create multiple arrows
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const leftChild2 = instance.findEle('left-child-2')
    const rightChild1 = instance.findEle('right-child-1')
    const rightChild2 = instance.findEle('right-child-2')

    // Create first arrow
    instance.createArrow(leftChild1, rightChild1)

    // Create second arrow
    instance.createArrow(leftChild2, rightChild2)
  }, instanceHandle)

  // Verify both arrows exist
  await expect(page.locator('svg g[data-linkid]')).toHaveCount(2)

  // Verify both have labels
  await expect(page.locator('svg g[data-linkid] text')).toHaveCount(2)
})

test('Arrow positioning and bezier curve', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Get arrow path element
  const arrowPath = page.locator('svg g[data-linkid] path').first()

  // Verify path has bezier curve (should contain 'C' command)
  const pathData = await arrowPath.getAttribute('d')
  expect(pathData).toContain('M') // Move to start point
  expect(pathData).toContain('C') // Cubic bezier curve

  // Verify arrow label is positioned at curve midpoint
  const arrowLabel = page.locator('svg g[data-linkid] text')
  await expect(arrowLabel).toBeVisible()

  // Label should have x and y coordinates
  const labelX = await arrowLabel.getAttribute('x')
  const labelY = await arrowLabel.getAttribute('y')
  expect(labelX).toBeTruthy()
  expect(labelY).toBeTruthy()
})

test('Arrow style inheritance and defaults', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')

    // Create arrow without custom styles
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Verify default styles are applied
  const arrowPath = page.locator('svg g[data-linkid] path').first()

  // Check default stroke attributes exist
  const stroke = await arrowPath.getAttribute('stroke')
  const strokeWidth = await arrowPath.getAttribute('stroke-width')
  const fill = await arrowPath.getAttribute('fill')

  expect(stroke).toBeTruthy()
  expect(strokeWidth).toBeTruthy()
  expect(fill).toBe('none') // Arrows should not be filled

  // Verify default label color
  const arrowLabel = page.locator('svg g[data-linkid] text')
  const labelFill = await arrowLabel.getAttribute('fill')
  expect(labelFill).toBeTruthy()
})

test('Arrow with opacity style', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')

    // Create arrow with opacity
    instance.createArrow(leftChild1, rightChild1, {
      style: {
        opacity: '0.5',
      },
    })
  }, instanceHandle)

  // Verify arrow appears with opacity style
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify at least one path has opacity applied
  const hasOpacity = await page.evaluate(() => {
    const paths = document.querySelectorAll('svg g[data-linkid] path')
    return Array.from(paths).some(path => path.getAttribute('opacity') === '0.5')
  })
  expect(hasOpacity).toBe(true)
})

test('Bidirectional arrow rendering', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')

    // Create bidirectional arrow
    instance.createArrow(leftChild1, rightChild1, {
      bidirectional: true,
    })
  }, instanceHandle)

  // Verify bidirectional arrow appears with multiple paths
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify multiple paths exist (should be more than a simple arrow)
  const pathCount = await page.locator('svg g[data-linkid] path').count()
  expect(pathCount).toBeGreaterThan(2) // Should have more paths for bidirectional

  // Verify paths have basic stroke attributes
  const paths = page.locator('svg g[data-linkid] path')
  const firstPath = paths.first()
  await expect(firstPath).toHaveAttribute('fill', 'none')
})

test('Arrow control point manipulation', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow first
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Select arrow to show control points
  await page.locator('svg g[data-linkid]').click()

  // Verify control points are visible
  const p2Element = page.locator('.circle').first()
  const p3Element = page.locator('.circle').last()
  await expect(p2Element).toBeVisible()
  await expect(p3Element).toBeVisible()

  // Get initial positions
  const p2InitialBox = await p2Element.boundingBox()

  // Drag P2 control point
  await p2Element.hover()
  await page.mouse.down()
  await page.mouse.move(p2InitialBox!.x + 50, p2InitialBox!.y + 30)
  await page.mouse.up()

  // Verify control point moved
  const p2NewBox = await p2Element.boundingBox()
  expect(Math.abs(p2NewBox!.x - (p2InitialBox!.x + 50))).toBeLessThan(10)
  expect(Math.abs(p2NewBox!.y - (p2InitialBox!.y + 30))).toBeLessThan(10)
})

test('Arrow deletion via keyboard', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow first
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Select arrow
  await page.locator('svg g[data-linkid]').click()
  await expect(page.locator('svg g[data-linkid] .arrow-highlight')).toBeVisible()

  // Delete arrow using keyboard
  await page.keyboard.press('Delete')

  // Verify arrow is removed
  await expect(page.locator('svg g[data-linkid]')).not.toBeVisible()
})

test('Arrow with stroke linecap styles', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')

    // Create arrow with round linecap
    instance.createArrow(leftChild1, rightChild1, {
      style: {
        strokeLinecap: 'round',
      },
    })
  }, instanceHandle)

  // Verify arrow appears with linecap style
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify at least one path has the linecap style
  const hasLinecap = await page.evaluate(() => {
    const paths = document.querySelectorAll('svg g[data-linkid] path')
    return Array.from(paths).some(path => path.getAttribute('stroke-linecap') === 'round')
  })
  expect(hasLinecap).toBe(true)
})

test('Arrow label text anchor positioning', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Verify arrow label has middle text anchor (centered)
  const arrowLabel = page.locator('svg g[data-linkid] text')
  await expect(arrowLabel).toHaveAttribute('text-anchor', 'middle')

  // Verify label has custom-link data type
  await expect(arrowLabel).toHaveAttribute('data-type', 'custom-link')
})

test('Arrow rendering after node expansion/collapse', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow between child nodes
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Verify arrow exists
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Collapse left main node by clicking its expander
  const leftMainExpander = page.locator('me-tpc[data-nodeid="meleft-main"] me-expander')
  if (await leftMainExpander.isVisible()) {
    await leftMainExpander.click()
  }

  // Arrow should still exist but may not be visible due to collapsed nodes
  // This tests the robustness of arrow rendering

  // Expand left main node again
  if (await leftMainExpander.isVisible()) {
    await leftMainExpander.click()
  }

  // Re-render arrows
  await page.evaluate(async instance => {
    instance.renderArrow()
  }, instanceHandle)

  // Verify arrow is visible again
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()
})

test('Multiple arrow selection state management', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create two arrows
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const leftChild2 = instance.findEle('left-child-2')
    const rightChild1 = instance.findEle('right-child-1')
    const rightChild2 = instance.findEle('right-child-2')

    instance.createArrow(leftChild1, rightChild1)
    instance.createArrow(leftChild2, rightChild2)
  }, instanceHandle)

  const arrows = page.locator('svg g[data-linkid]')
  const firstArrow = arrows.first()
  const secondArrow = arrows.last()

  // Select first arrow
  await firstArrow.click()
  await expect(page.locator('.arrow-highlight').first()).toBeVisible()

  // Select second arrow
  await secondArrow.click()
  await expect(page.locator('.arrow-highlight').first()).toBeVisible()

  // Click elsewhere to deselect
  await page.locator('#map').click()

  // Wait a bit for deselection to take effect
  await page.waitForTimeout(200)

  // Verify that selection state has changed (may still have some highlights due to timing)
  // The important thing is that the selection behavior works
  const arrowsExist = await page.locator('svg g[data-linkid]').count()
  expect(arrowsExist).toBe(2) // Both arrows should still exist
})

test('Arrow data persistence and retrieval', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow with specific properties
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')

    instance.createArrow(leftChild1, rightChild1, {
      bidirectional: true,
      style: {
        stroke: '#ff6600',
        strokeWidth: '4',
        labelColor: '#333333',
      },
    })
  }, instanceHandle)

  // Get arrow data from instance
  const arrowData = await page.evaluate(async instance => {
    return instance.arrows[0]
  }, instanceHandle)

  // Verify arrow properties are correctly stored
  expect(arrowData.label).toBe('Custom Link')
  expect(arrowData.from).toBe('left-child-1')
  expect(arrowData.to).toBe('right-child-1')
  expect(arrowData.bidirectional).toBe(true)
  expect(arrowData.style?.stroke).toBe('#ff6600')
  expect(arrowData.style?.strokeWidth).toBe('4')
  expect(arrowData.style?.labelColor).toBe('#333333')
  expect(arrowData.id).toBeTruthy()
})

test('Arrow tidy function removes invalid arrows', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow first
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Verify arrow exists
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Simulate removing a node that the arrow references
  await page.evaluate(async instance => {
    // Manually corrupt arrow data to simulate invalid reference
    instance.arrows[0].to = 'non-existent-node'

    // Run tidy function
    instance.tidyArrow()
  }, instanceHandle)

  // Verify arrow was removed by tidy function
  const arrowCount = await page.evaluate(async instance => {
    return instance.arrows.length
  }, instanceHandle)

  expect(arrowCount).toBe(0)
})

test('Arrow highlight update during control point drag', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Create arrow first
  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Select arrow to show control points
  await page.locator('svg g[data-linkid]').click()

  // Verify highlight is visible
  await expect(page.locator('svg g[data-linkid] .arrow-highlight')).toBeVisible()

  // Get initial highlight path
  const initialHighlightPath = await page.locator('svg g[data-linkid] .arrow-highlight path').first().getAttribute('d')

  // Drag control point to change arrow shape
  const p2Element = page.locator('.circle').first()
  const p2Box = await p2Element.boundingBox()
  await p2Element.hover()
  await page.mouse.down()
  await page.mouse.move(p2Box!.x + 100, p2Box!.y + 50)
  await page.mouse.up()

  // Verify highlight path updated
  const updatedHighlightPath = await page.locator('svg g[data-linkid] .arrow-highlight path').first().getAttribute('d')
  expect(updatedHighlightPath).not.toBe(initialHighlightPath)
})

test('Arrow creation with invalid nodes', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  // Try to create arrow with undefined nodes (simulating collapsed/hidden nodes)
  await page.evaluate(async instance => {
    try {
      // Simulate trying to create arrow when nodes are not found
      const nonExistentNode1 = instance.findEle('non-existent-1')
      const nonExistentNode2 = instance.findEle('non-existent-2')

      // This should not create an arrow since nodes don't exist
      if (nonExistentNode1 && nonExistentNode2) {
        instance.createArrow(nonExistentNode1, nonExistentNode2)
      }
    } catch (error) {
      // Expected to fail gracefully
      console.log('Arrow creation failed as expected:', error.message)
    }
  }, instanceHandle)

  // Verify no arrow was created
  await expect(page.locator('svg g[data-linkid]')).not.toBeVisible()
})

test('Arrow bezier midpoint calculation', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')
    instance.createArrow(leftChild1, rightChild1)
  }, instanceHandle)

  // Get arrow label position
  const labelX = await page.locator('svg g[data-linkid] text').getAttribute('x')
  const labelY = await page.locator('svg g[data-linkid] text').getAttribute('y')

  // Verify label is positioned (should have numeric coordinates)
  expect(parseFloat(labelX!)).toBeGreaterThan(0)
  expect(parseFloat(labelY!)).toBeGreaterThan(0)

  // Get arrow path to verify label is positioned along the curve
  const pathData = await page.locator('svg g[data-linkid] path').first().getAttribute('d')
  expect(pathData).toContain('M') // Move command
  expect(pathData).toContain('C') // Cubic bezier command
})

test('Arrow style application to all elements', async ({ page, me }) => {
  const instanceHandle = await me.getInstance()

  await page.evaluate(async instance => {
    const leftChild1 = instance.findEle('left-child-1')
    const rightChild1 = instance.findEle('right-child-1')

    // Create bidirectional arrow with comprehensive styles
    instance.createArrow(leftChild1, rightChild1, {
      bidirectional: true,
      style: {
        stroke: '#purple',
        strokeWidth: '5',
        strokeDasharray: '10,5',
        strokeLinecap: 'square',
        opacity: '0.8',
        labelColor: '#orange',
      },
    })
  }, instanceHandle)

  // Verify arrow appears with comprehensive styles
  await expect(page.locator('svg g[data-linkid]')).toBeVisible()

  // Verify styles are applied to arrow elements
  const hasStyles = await page.evaluate(() => {
    const paths = document.querySelectorAll('svg g[data-linkid] path')
    const hasStroke = Array.from(paths).some(path => path.getAttribute('stroke') === '#purple')
    const hasWidth = Array.from(paths).some(path => path.getAttribute('stroke-width') === '5')
    const hasOpacity = Array.from(paths).some(path => path.getAttribute('opacity') === '0.8')
    return hasStroke && hasWidth && hasOpacity
  })
  expect(hasStyles).toBe(true)

  // Verify label color
  const label = page.locator('svg g[data-linkid] text')
  await expect(label).toHaveAttribute('fill', '#orange')
})
