import { createShapeId } from '@tldraw/editor'
import { vi } from 'vitest'
import { SelectTool } from '../lib/tools/SelectTool/SelectTool'
import { TestEditor } from './TestEditor'

let editor: TestEditor

beforeEach(() => {
	editor = new TestEditor()
})
afterEach(() => {
	editor?.dispose()
})

describe(SelectTool, () => {
	describe('pointer down while shape is being edited', () => {
		it('captures the pointer down event if it is on the shape', () => {
			editor.setCurrentTool('geo').pointerDown(0, 0).pointerMove(100, 100).pointerUp(100, 100)
			const shapeId = editor.getLastCreatedShape().id
			editor._transformPointerDownSpy.mockRestore()
			editor._transformPointerUpSpy.mockRestore()
			editor.setCurrentTool('select')
			editor.expectToBeIn('select.idle')
			editor.doubleClick(50, 50, shapeId)

			expect(editor.getCurrentPageState().editingShapeId).toBe(shapeId)

			// note: this behavior has moved to the React hook useEditablePlainText.
			// clicking on the input will preserve selection, however you can
			// click on the shape itself to select it as usual.
			// clicking on the shape should not do anything
			// vi.advanceTimersByTime(1000)
			// editor.pointerDown(50, 50, shapeId)
			// expect(editor.currentPageState.editingShapeId).toBe(shapeId)

			// clicking outside the shape should end editing
			vi.advanceTimersByTime(1000)

			editor.pointerDown(150, 150).pointerUp()
			expect(editor.getCurrentPageState().editingShapeId).toBe(null)
			editor.expectToBeIn('select.idle')
		})
	})
	it('does not allow pressing undo to end up in the editing state', () => {
		editor.setCurrentTool('geo').pointerDown(0, 0).pointerMove(100, 100).pointerUp(100, 100)
		const shapeId = editor.getLastCreatedShape().id
		editor._transformPointerDownSpy.mockRestore()
		editor._transformPointerUpSpy.mockRestore()
		editor.setCurrentTool('select')
		editor.doubleClick(50, 50, shapeId)

		expect(editor.getCurrentPageState().editingShapeId).toBe(shapeId)

		// clicking outside the shape should end editing
		vi.advanceTimersByTime(1000)

		editor.pointerDown(150, 150).pointerUp()
		expect(editor.getCurrentPageState().editingShapeId).toBe(null)
		editor.expectToBeIn('select.idle')

		editor.undo()

		expect(editor.getCurrentPageState().editingShapeId).toBe(null)
	})
})

describe('When pointing a shape behind the current selection', () => {
	it('Does not select on pointer down, but does select on pointer up', () => {
		editor.selectNone()
		const ids = {
			A: createShapeId('A'),
			B: createShapeId('B'),
			C: createShapeId('C'),
		}
		editor.createShapes([
			{ id: ids.A, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } },
			{ id: ids.B, type: 'geo', x: 50, y: 50, props: { w: 100, h: 100 } },
			{ id: ids.C, type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } },
		])
		editor.select(ids.A, ids.C)
		// don't select it yet! It's behind the current selection
		editor.pointerDown(75, 75)
		expect(editor.getSelectedShapeIds()).toMatchObject([ids.A, ids.C])
		editor.pointerUp(75, 75)
		expect(editor.getSelectedShapeIds()).toMatchObject([ids.B])
	})

	it('Selects on shift+pointer up', () => {
		editor.selectNone()
		const ids = {
			A: createShapeId('A'),
			B: createShapeId('B'),
			C: createShapeId('C'),
		}
		editor.createShapes([
			{ id: ids.A, type: 'geo', x: 0, y: 0, props: { w: 50, h: 50 } },
			{ id: ids.B, type: 'geo', x: 50, y: 50, props: { w: 50, h: 50 } },
			{ id: ids.C, type: 'geo', x: 100, y: 100, props: { w: 50, h: 50 } },
		])
		editor.select(ids.A, ids.C)

		// don't select B yet! It's behind the current selection
		editor.pointerDown(75, 75, { target: 'canvas' }, { shiftKey: true })
		editor.expectToBeIn('select.pointing_selection')
		expect(editor.getSelectedShapeIds()).toMatchObject([ids.A, ids.C])

		editor.pointerUp(75, 75, { target: 'canvas' }, { shiftKey: true })
		editor.expectToBeIn('select.idle')
		expect(editor.getSelectedShapeIds()).toMatchObject([ids.A, ids.C, ids.B])

		// and deselect
		editor.pointerDown(75, 75, { target: 'canvas' }, { shiftKey: true })
		editor.expectToBeIn('select.pointing_shape')
		expect(editor.getSelectedShapeIds()).toMatchObject([ids.A, ids.C, ids.B])

		editor.pointerUp(75, 75, { target: 'canvas' }, { shiftKey: true })
		editor.expectToBeIn('select.idle')
		expect(editor.getSelectedShapeIds()).toMatchObject([ids.A, ids.C])
	})

	it('Moves on pointer move, does not select on pointer up', () => {
		editor.selectNone()
		const ids = {
			A: createShapeId('A'),
			B: createShapeId('B'),
			C: createShapeId('C'),
		}
		editor.createShapes([
			{ id: ids.A, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } },
			{ id: ids.B, type: 'geo', x: 50, y: 50, props: { w: 100, h: 100 } },
			{ id: ids.C, type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } },
		])
		editor.select(ids.A, ids.C) // don't select it yet! It's behind the current selection
		editor.pointerDown(100, 100, ids.B)
		editor.pointerMove(150, 150)
		editor.pointerMove(151, 151)
		editor.pointerMove(100, 100)
		expect(editor.getSelectedShapeIds()).toMatchObject([ids.A, ids.C])
		editor.pointerUp(100, 100, ids.B)
		expect(editor.getSelectedShapeIds()).toMatchObject([ids.A, ids.C]) // no change! we've moved
	})
})

describe('When brushing arrows', () => {
	it('Brushes a straight arrow', () => {
		const ids = {
			arrow1: createShapeId('arrow1'),
		}
		editor
			.selectAll()
			.deleteShapes(editor.getSelectedShapeIds())
			.setCamera({ x: 0, y: 0, z: 1 })
			.createShapes([
				{
					id: ids.arrow1,
					type: 'arrow',
					x: 0,
					y: 0,
					props: { start: { x: 0, y: 0 }, end: { x: 100, y: 100 }, bend: 0 },
				},
			])
		editor.setCurrentTool('select')
		editor.pointerDown(0, 45)
		editor.pointerMove(100, 55)
		editor.expectToBeIn('select.brushing')
		expect(editor.getSelectedShapeIds()).toStrictEqual([ids.arrow1])
	})

	it('Brushes within the curve of a curved arrow without selecting the arrow', () => {
		editor
			.selectAll()
			.deleteShapes(editor.getSelectedShapeIds())
			.setCamera({ x: 0, y: 0, z: 1 })
			.createShapes([
				{
					id: createShapeId('arrow1'),
					type: 'arrow',
					x: 0,
					y: 0,
					props: { start: { x: 0, y: 0 }, end: { x: 100, y: 100 }, bend: 40 },
				},
			])
		editor.setCurrentTool('select')
		editor.pointerDown(55, 45)
		editor.pointerMove(45, 55)
		editor.expectToBeIn('select.brushing')
		expect(editor.getSelectedShapeIds()).toStrictEqual([])
	})
})
