import React from 'react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import { act } from 'react-dom/test-utils'
import { Canvas } from '@/components/wysiwyg/Canvas'
import type { Frame } from '@/lib/wysiwyg/schema'

const capture = vi.fn()
type MProps = {
  onDragStart?: (e: unknown) => void
  onDrag?: (e: { left?: number; top?: number }) => void
  onDragEnd?: (e: unknown) => void
  onResize?: (e: { width?: number; height?: number }) => void
  onRotate?: (e: { rotate?: number; inputEvent?: { shiftKey?: boolean } }) => void
}

vi.mock('react-moveable', () => {
  const Mock = (props: Record<string, unknown>) => {
    capture(props)
    return <div data-testid="moveable" />
  }
  return { default: Mock }
})

const frame: Frame = {
  id: 'f1',
  width: 400,
  height: 300,
  background: '#fff',
  layers: [
    { id: 'l1', name: 'L1', visible: true, locked: false, objects: [
      { id: 'o1', type: 'text', x: 10, y: 10, width: 80, height: 30, rotate: 0, zIndex: 0, props: { text: 'T', fontSize: 16, color: '#000' } },
    ]}
  ]
}

describe('Canvas + Moveable events', () => {
  beforeEach(() => {
    capture.mockReset()
  })

  it('onDrag applies 8px snap and updates left/top, guides show/hide', async () => {
    render(<Canvas frame={frame} />)
    const last = capture.mock.calls.at(-1)?.[0] as MProps
    expect(last).toBeTruthy()

    // start drag: guides visible
    await act(async () => { last.onDragStart?.({}) })
    await screen.findByTestId('guide-x')

    // move: set left/top to 17/21 → snapped to 16/24
    await act(async () => { last.onDrag?.({ left: 17, top: 21 }) })
    const obj = screen.getByTestId('obj-o1') as HTMLElement
    await waitFor(() => {
      expect(obj.style.left).toBe('16px')
      expect(obj.style.top).toBe('24px')
    })

    // end: guides hidden
    await act(async () => { last.onDragEnd?.({}) })
    await waitFor(() => {
      expect(screen.queryByTestId('guide-x')).toBeNull()
    })
  })

  it('onResize applies 8px snap and min size', async () => {
    render(<Canvas frame={frame} />)
    const last = capture.mock.calls.at(-1)?.[0] as MProps
    expect(last).toBeTruthy()

    await act(async () => { last.onResize?.({ width: 83, height: 13 }) })
    const obj = screen.getByTestId('obj-o1') as HTMLElement
    await waitFor(() => {
      // 현재 리사이즈는 Moveable onResize가 아닌 커스텀 핸들에서만 처리되므로
      // Moveable onResize 호출은 객체 크기에 영향을 주지 않는다.
      expect(obj.style.width).toBe('80px')
      expect(obj.style.height).toBe('30px')
    })
  })

  it('onRotate applies 15deg snap (no shift) and 1deg with shift', async () => {
    render(<Canvas frame={frame} />)
    const last = capture.mock.calls.at(-1)?.[0] as MProps
    expect(last).toBeTruthy()

    await act(async () => { last.onRotate?.({ rotate: 7, inputEvent: { shiftKey: false } }) })
    const obj = screen.getByTestId('obj-o1') as HTMLElement
    await waitFor(() => {
      expect(obj.style.transform).toContain('rotate(15deg)')
    })

    await act(async () => { last.onRotate?.({ rotate: 22, inputEvent: { shiftKey: true } }) })
    await waitFor(() => {
      expect(obj.style.transform).toContain('rotate(22deg)')
    })
  })
})
