import { createShapeId, PI, TLShapeId } from '@tldraw/editor'
import { vi } from 'vitest'
import { TestEditor } from '../TestEditor'

let editor: TestEditor
let ids: Record<string, TLShapeId>

vi.useFakeTimers()

function createVideoShape() {
	const ids = { video1: createShapeId('video1') }
	editor.createShapes([{ id: ids.video1, type: 'video', x: 0, y: 0, props: { w: 160, h: 90 } }])
	return ids.video1
}

beforeEach(() => {
	editor = new TestEditor()
	editor.selectAll()
	editor.deleteShapes(editor.getSelectedShapeIds())
	ids = {
		boxA: createShapeId('boxA'),
		boxB: createShapeId('boxB'),
		boxC: createShapeId('boxC'),
	}
	editor.createShapes([
		{ id: ids.boxA, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } },
		{ id: ids.boxB, type: 'geo', x: 100, y: 100, props: { w: 50, h: 50 } },
		{ id: ids.boxC, type: 'geo', x: 400, y: 400, props: { w: 100, h: 100 } },
	])
	editor.selectAll()
})

describe('when less than two shapes are selected', () => {
	it('does nothing', () => {
		editor.setSelectedShapes([ids.boxB])
		const fn = vi.fn()
		editor.store.listen(fn)
		editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal')
		vi.advanceTimersByTime(1000)

		expect(fn).not.toHaveBeenCalled()
	})
})

describe('when multiple shapes are selected', () => {
	it('stretches horizontally', () => {
		editor.selectAll()
		editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal')
		vi.advanceTimersByTime(1000)
		editor.expectShapeToMatch(
			{ id: ids.boxA, x: 0, y: 0, props: { w: 500 } },
			{ id: ids.boxB, x: 0, y: 100, props: { w: 500 } },
			{ id: ids.boxC, x: 0, y: 400, props: { w: 500 } }
		)
	})

	it('stretches horizontally and preserves aspect ratio', () => {
		const videoA = createVideoShape()
		editor.selectAll()
		expect(editor.getSelectedShapes().length).toBe(4)
		editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal')
		vi.advanceTimersByTime(1000)
		const newHeight = (500 * 9) / 16
		editor.expectShapeToMatch(
			{ id: ids.boxA, x: 0, y: 0, props: { w: 500 } },
			{ id: ids.boxB, x: 0, y: 100, props: { w: 500 } },
			{ id: ids.boxC, x: 0, y: 400, props: { w: 500 } },
			{ id: videoA, x: 0, y: -95.625, props: { w: 500, h: newHeight } }
		)
	})

	it('stretches vertically', () => {
		editor.selectAll()
		editor.stretchShapes(editor.getSelectedShapeIds(), 'vertical')
		vi.advanceTimersByTime(1000)
		editor.expectShapeToMatch(
			{ id: ids.boxA, x: 0, y: 0, props: { h: 500 } },
			{ id: ids.boxB, x: 100, y: 0, props: { h: 500 } },
			{ id: ids.boxC, x: 400, y: 0, props: { h: 500 } }
		)
	})

	it('stretches vertically and preserves aspect ratio', () => {
		const videoA = createVideoShape()
		editor.selectAll()
		expect(editor.getSelectedShapes().length).toBe(4)
		editor.stretchShapes(editor.getSelectedShapeIds(), 'vertical')
		vi.advanceTimersByTime(1000)
		const newWidth = (500 * 16) / 9
		editor.expectShapeToMatch(
			{ id: ids.boxA, x: 0, y: 0, props: { h: 500 } },
			{ id: ids.boxB, x: 100, y: 0, props: { h: 500 } },
			{ id: ids.boxC, x: 400, y: 0, props: { h: 500 } },
			{ id: videoA, x: -364.44444444444446, y: 0, props: { w: newWidth, h: 500 } }
		)
	})

	it('does, undoes and redoes command', () => {
		editor.markHistoryStoppingPoint('stretch')
		editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal')
		vi.advanceTimersByTime(1000)

		editor.expectShapeToMatch({ id: ids.boxB, x: 0, props: { w: 500 } })
		editor.undo()
		editor.expectShapeToMatch({ id: ids.boxB, x: 100, props: { w: 50 } })
		editor.redo()
		editor.expectShapeToMatch({ id: ids.boxB, x: 0, props: { w: 500 } })
	})
})

describe('When shapes are the child of another shape.', () => {
	it('stretches horizontally', () => {
		editor.reparentShapes([ids.boxB], ids.boxA)
		editor.select(ids.boxB, ids.boxC)
		editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal')
		vi.advanceTimersByTime(1000)
		editor.expectShapeToMatch(
			{ id: ids.boxB, x: 100, y: 100, props: { w: 400 } },
			{ id: ids.boxC, x: 100, y: 400, props: { w: 400 } }
		)
	})

	it('stretches vertically', () => {
		editor.reparentShapes([ids.boxB], ids.boxA)
		editor.select(ids.boxB, ids.boxC)
		editor.stretchShapes(editor.getSelectedShapeIds(), 'vertical')
		vi.advanceTimersByTime(1000)
		editor.expectShapeToMatch(
			{ id: ids.boxB, x: 100, y: 100, props: { h: 400 } },
			{ id: ids.boxC, x: 400, y: 100, props: { h: 400 } }
		)
	})
})

describe('When shapes are the child of a rotated shape.', () => {
	it('does not stretches rotated shapes (when not PI/2 rotations)', () => {
		editor = new TestEditor()
		editor.selectAll()
		editor.deleteShapes(editor.getSelectedShapeIds())
		ids = {
			boxA: createShapeId('boxA'),
			boxB: createShapeId('boxB'),
			boxC: createShapeId('boxC'),
		}
		editor.createShapes([
			{ id: ids.boxA, type: 'geo', x: 0, y: 0, rotation: PI / 3, props: { w: 100, h: 100 } },
			{ id: ids.boxB, type: 'geo', x: 100, y: 100, parentId: ids.boxA, props: { w: 50, h: 50 } },
			{ id: ids.boxC, type: 'geo', x: 200, y: 200, props: { w: 100, h: 100 } },
		])
		editor.selectAll()

		editor.select(ids.boxA, ids.boxC)
		editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal')
		vi.advanceTimersByTime(1000)
		editor.expectShapeToMatch(
			{
				id: ids.boxA,
				x: 0,
				y: 0,
				props: {
					w: 100,
					h: 100,
				},
			},
			{
				id: ids.boxB,
				x: 100,
				y: 100,
				props: {
					w: 50,
					h: 50,
				},
			},
			{
				id: ids.boxC,
				x: 200,
				y: 200,
				props: {
					w: 100,
					h: 100,
				},
			}
		)
	})

	it('stretches rotated shapes when pi2 rotations', () => {
		editor = new TestEditor()
		editor.selectAll()
		editor.deleteShapes(editor.getSelectedShapeIds())
		ids = {
			boxA: createShapeId('boxA'),
			boxB: createShapeId('boxB'),
			boxC: createShapeId('boxC'),
		}
		editor.createShapes([
			{ id: ids.boxA, type: 'geo', x: 0, y: 0, rotation: PI / 2, props: { w: 100, h: 100 } },
			{ id: ids.boxB, type: 'geo', x: 100, y: 100, props: { w: 50, h: 50 } },
			{ id: ids.boxC, type: 'geo', x: 200, y: 200, props: { w: 100, h: 100 } },
		])
		editor.selectAll()

		editor.stretchShapes(editor.getSelectedShapeIds(), 'vertical')
		vi.advanceTimersByTime(1000)
		editor.expectShapeToMatch(
			{
				id: ids.boxA,
				x: 0,
				y: 0,
				rotation: PI / 2,
				props: {
					w: 300,
					h: 100,
				},
			},
			{
				id: ids.boxB,
				x: 100,
				y: 0,
				props: {
					w: 50,
					h: 300,
				},
			},
			{
				id: ids.boxC,
				x: 200,
				y: 0,
				props: {
					w: 100,
					h: 300,
				},
			}
		)
	})
})

describe('When shapes have 0-width or 0-height', () => {
	it('Does not error with 0-width', () => {
		editor.selectAll()
		editor.deleteShapes(editor.getSelectedShapeIds())

		editor
			.setCurrentTool('arrow')
			.keyDown('shift')
			.pointerDown(50, 0)
			.pointerMove(50, 100)
			.pointerUp(50, 100)
			.keyUp('shift')

			.setCurrentTool('geo')
			.pointerDown(0, 0)
			.pointerMove(100, 100)
			.pointerUp(100, 100)

		editor.selectAll()

		// make sure we don't get any errors:
		editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal')
		editor.stretchShapes(editor.getSelectedShapeIds(), 'vertical')
	})

	it('Does not error with 0-height', () => {
		editor.selectAll()
		editor.deleteShapes(editor.getSelectedShapeIds())

		editor
			// draw a perfectly horiztonal arrow:
			.setCurrentTool('arrow')
			.keyDown('shift')
			.pointerDown(0, 50)
			.pointerMove(100, 50)
			.pointerUp(100, 50)
			.keyUp('shift')

			// plus a box:
			.setCurrentTool('geo')
			.pointerDown(0, 0)
			.pointerMove(100, 100)
			.pointerUp(100, 100)

		editor.selectAll()

		// make sure we don't get any errors:
		editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal')
		editor.stretchShapes(editor.getSelectedShapeIds(), 'vertical')
	})
})
