import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { act } from 'react-dom/test-utils';

/* Same DOM-harness pattern as MediaStylePanel.video.test.tsx -- mock
   @craftjs/core's useEditor so setProp calls can be observed without
   mounting a real <Editor> tree, and mock utils/assets so AssetPicker
   doesn't hit the network. */
const setPropSpy = vi.fn((_id: string, updater: (p: any) => void) => {
  updater(lastProps);
});
let lastProps: any;

vi.mock('@craftjs/core', () => ({
  useEditor: () => ({ actions: { setProp: setPropSpy } }),
}));

vi.mock('../../../utils/assets', () => ({
  uploadAsset: vi.fn(),
  listAssets: vi.fn(),
}));

import { ImageStylePanel } from './ImageStylePanel';

let container: HTMLDivElement;
let root: Root;

function render(ui: React.ReactElement) {
  container = document.createElement('div');
  document.body.appendChild(container);
  act(() => {
    root = createRoot(container);
    root.render(ui);
  });
}

function unmount() {
  act(() => { root.unmount(); });
  container.remove();
}

function q<T extends Element = Element>(testId: string): T | null {
  return container.querySelector(`[data-testid="${testId}"]`);
}

function qAll<T extends Element = Element>(testId: string): T[] {
  return Array.from(container.querySelectorAll(`[data-testid="${testId}"]`));
}

/** Click the preset button with this exact label inside a given data-testid
 *  root (AspectRatioControl / PresetButtonGrid render plain buttons keyed by
 *  label, no per-button testid). */
function clickPresetByLabel(root: Element | null, label: string) {
  const btn = Array.from(root?.querySelectorAll('button') ?? []).find((b) => b.textContent === label);
  expect(btn).toBeTruthy();
  act(() => { btn!.dispatchEvent(new MouseEvent('click', { bubbles: true })); });
}

beforeEach(() => {
  setPropSpy.mockClear();
});

afterEach(() => {
  if (container) unmount();
});

describe('ImageStylePanel crop-fills-by-default (fix-anim-image B)', () => {
  test('applying a non-empty aspect ratio with objectFit unset also sets objectFit to cover', () => {
    lastProps = { src: '/uploads/photo.jpg', alt: '', style: {} };
    render(<ImageStylePanel selectedId="img-1" nodeProps={lastProps} />);

    clickPresetByLabel(q('aspect-ratio-control'), '1:1');

    expect(lastProps.style.aspectRatio).toBe('1 / 1');
    expect(lastProps.style.objectFit).toBe('cover');
  });

  test('applying a ratio when objectFit is already "contain" leaves it as contain (no forced override)', () => {
    lastProps = { src: '/uploads/photo.jpg', alt: '', style: { objectFit: 'contain' } };
    render(<ImageStylePanel selectedId="img-1" nodeProps={lastProps} />);

    clickPresetByLabel(q('aspect-ratio-control'), '16:9');

    expect(lastProps.style.aspectRatio).toBe('16 / 9');
    expect(lastProps.style.objectFit).toBe('contain');
  });

  test('clearing the ratio (Original) does not force-clear objectFit', () => {
    lastProps = { src: '/uploads/photo.jpg', alt: '', style: { aspectRatio: '1 / 1', objectFit: 'cover' } };
    render(<ImageStylePanel selectedId="img-1" nodeProps={lastProps} />);

    clickPresetByLabel(q('aspect-ratio-control'), 'Original');

    expect(lastProps.style.aspectRatio).toBe('');
    expect(lastProps.style.objectFit).toBe('cover');
  });

  test('the Object Fit control (Cover/Contain/Fill/None) remains present and usable', () => {
    lastProps = { src: '/uploads/photo.jpg', alt: '', style: { aspectRatio: '1 / 1', objectFit: 'cover' } };
    render(<ImageStylePanel selectedId="img-1" nodeProps={lastProps} />);

    // PresetButtonGrid buttons have no dedicated per-button testid; find by label text.
    const containBtn = Array.from(container.querySelectorAll('button')).find((b) => b.textContent === 'Contain');
    expect(containBtn).toBeTruthy();
    act(() => { containBtn!.dispatchEvent(new MouseEvent('click', { bubbles: true })); });
    expect(lastProps.style.objectFit).toBe('contain');
  });
});

describe('ImageStylePanel Height control gated on aspect-ratio (fix-anim-image C)', () => {
  test('no aspect-ratio set: both Width and Height SizeControls render', () => {
    lastProps = { src: '/uploads/photo.jpg', alt: '', style: {} };
    render(<ImageStylePanel selectedId="img-1" nodeProps={lastProps} />);
    expect(qAll('size-control').length).toBe(2);
  });

  test('aspect-ratio set: only the Width SizeControl renders (Height is hidden)', () => {
    lastProps = { src: '/uploads/photo.jpg', alt: '', style: { aspectRatio: '1 / 1', objectFit: 'cover' } };
    render(<ImageStylePanel selectedId="img-1" nodeProps={lastProps} />);
    expect(qAll('size-control').length).toBe(1);
  });

  test('applying an aspect ratio clears a stale height so it cannot linger and conflict', () => {
    lastProps = { src: '/uploads/photo.jpg', alt: '', style: { height: '50%' } };
    render(<ImageStylePanel selectedId="img-1" nodeProps={lastProps} />);

    clickPresetByLabel(q('aspect-ratio-control'), '9:16');

    expect(lastProps.style.aspectRatio).toBe('9 / 16');
    expect(lastProps.style.height).toBe('');
  });
});
