/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Utils } from '@tldraw/core'
import { TLDR } from '~state/TLDR'
import { mockDocument, TldrawTestApp } from '~test'
import { ColorStyle, DashStyle, SessionType, SizeStyle, TDShapeType } from '~types'

let app: TldrawTestApp

beforeEach(() => {
  app = new TldrawTestApp()
  app.loadDocument(mockDocument)
})

describe('insert command', () => {
  it('Inserts shapes, bindings, etc. into the current page', () => {
    const content = app.getContent()!
    const size = app.shapes.length
    app.insertContent(content)
    expect(app.shapes.length).toBe(size * 2)
  })

  it('Selects content when opts.select is true', () => {
    const content = app.getContent()!
    const size = app.shapes.length
    const prevSelectedIds = [...app.selectedIds]
    app.insertContent(content, { select: true })
    expect(app.shapes.length).toBe(size * 2)
    expect(app.selectedIds).not.toMatchObject(prevSelectedIds)
  })

  it('Centers inserted content at a point', () => {
    app.select('rect1')
    const content = app.getContent()!

    const before = [...app.shapes]

    const point = [222, 444]

    app.insertContent(content, { point })

    const inserted = [...app.shapes].filter((s) => !before.includes(s))

    expect(
      Utils.getBoundsCenter(Utils.getCommonBounds(inserted.map(TLDR.getBounds)))
    ).toMatchObject(point)
  })

  it('does nothing when ids are explicitly empty', () => {
    const content = app.getContent([])
    expect(content).toBe(undefined)
  })

  it('uses the selected ids when no ids provided', () => {
    app.select('rect1')
    const content = app.getContent()!
    const size = app.shapes.length
    app.insertContent(content)
    expect(app.shapes.length).toBe(size + 1)
  })

  it('uses all shape ids from the page when no selection, either', () => {
    app.selectNone()
    const content = app.getContent()!
    const size = app.shapes.length
    app.insertContent(content)
    expect(app.shapes.length).toBe(size * 2)
  })

  it('does nothing if the page has no shapes, either', () => {
    app.deleteAll()
    const content = app.getContent()
    expect(content).toBe(undefined)
  })

  it('includes bindings', () => {
    app
      .createShapes({
        type: TDShapeType.Arrow,
        id: 'arrow1',
        point: [200, 200],
      })
      .select('arrow1')
      .movePointer([200, 200])
      .startSession(SessionType.Arrow, 'arrow1', 'start')
      .movePointer([50, 50])
      .completeSession()
      .selectNone()

    expect(app.bindings.length).toBe(1)

    const content = app.getContent()!
    const size = app.shapes.length

    app.insertContent(content)
    expect(app.bindings.length).toBe(2)
    expect(app.shapes.length).toBe(size * 2)
  })

  it('removes bindings when only one shape is inserted', () => {
    app
      .createShapes({
        type: TDShapeType.Arrow,
        id: 'arrow1',
        point: [200, 200],
      })
      .select('arrow1')
      .movePointer([200, 200])
      .startSession(SessionType.Arrow, 'arrow1', 'start')
      .movePointer([50, 50])
      .completeSession()

    expect(app.bindings.length).toBe(1) // arrow1 -> rect3

    app.select('rect3') // select only rect3, not arrow1

    const content = app.getContent()!

    // getContent DOES include the incomplete binding
    expect(Object.values(content.bindings).length).toBe(1)

    app.insertContent(content)

    // insertContent does not paste in the discarded binding
    expect(app.bindings.length).toBe(1)
  })

  it('works with groups', () => {
    app.select('rect1', 'rect2').group().selectAll()

    const content = app.getContent()!

    const size = app.shapes.length

    app.insertContent(content)

    expect(app.shapes.length).toBe(size * 2)
  })

  it('if a shapes parent is not inserted, inserts to the page instead', () => {
    app.select('rect1', 'rect2').group().select('rect1')

    const content = app.getContent()!

    // insertContent discards the incomplete binding
    const size = app.shapes.length

    const before = [...app.shapes]

    app.insertContent(content)

    expect(app.shapes.length).toBe(size + 1)

    const inserted = [...app.shapes].filter((s) => !before.includes(s))[0]

    expect(inserted.parentId).toBe(app.currentPageId)
  })

  it('does not add groups without children', () => {
    // insertContent discards the incomplete binding
    const size = app.shapes.length

    app.insertContent({
      shapes: [
        {
          id: '935ff424-bf40-4e2d-3bfb-d26061150b03',
          type: TDShapeType.Group,
          name: 'Group',
          parentId: 'currentPageId',
          childIndex: 1,
          point: [0, 0],
          size: [100, 100],
          rotation: 0,
          children: [],
          style: {
            color: ColorStyle.Black,
            size: SizeStyle.Small,
            isFilled: false,
            dash: DashStyle.Dashed,
            scale: 1,
          },
        },
      ],
    })

    expect(app.shapes.length).toBe(size)
  })
})

describe('When opts.overwrite is true', () => {
  it('replaces content', () => {
    const content = app.getContent()!
    const size = app.shapes.length
    const ids = app.shapes.map((s) => s.id)
    app.insertContent(content, { overwrite: true })
    expect(app.shapes.length).toBe(size)
    expect(app.shapes.map((s) => s.id)).toMatchObject(ids)
  })

  it('restores content under the same ids', () => {
    const content = app.getContent()!
    const ids = app.shapes.map((s) => s.id)
    app.deleteAll().insertContent(content, { overwrite: true })
    expect(app.shapes.map((s) => s.id)).toMatchObject(ids)
  })
})
