import { render, waitFor } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { describe, expect, test, vi } from 'vitest';
import {
  UiDialog,
  UiDialogClose,
  UiDialogContent,
  UiDialogDescription,
  UiDialogHeader,
  UiDialogTitle,
  UiDialogTrigger,
} from '../index';
import type { RenderResult } from '@testing-library/vue';
import { UiButton } from '../../UiButton';

/**
 * Renders a minimal dialog for tests (trigger + content with title/description/actions).
 * @param {object} [options] - Options.
 * @param {boolean} [options.open] - When set, the dialog starts open.
 * @returns {RenderResult} Testing Library render result.
 */
function renderDialog(options: { open?: boolean } = {}): RenderResult {
  const { open } = options;

  return render({
    components: {
      UiDialog,
      UiDialogClose,
      UiDialogContent,
      UiDialogTrigger,
      UiButton,
    },
    template: `
      <UiDialog ${open !== undefined ? ':open="open"' : ''}>
        <UiDialogTrigger>Open</UiDialogTrigger>
        <UiDialogContent title="Dialog title" description="Dialog description">
          <p>Body</p>
          <template #actions>
            <UiDialogClose as-child>
              <UiButton type="button" variant="outline">Cancel</UiButton>
            </UiDialogClose>
          </template>
        </UiDialogContent>
      </UiDialog>
    `,
    setup() {
      return { open };
    },
  });
}

/**
 * Renders dialog with `update:open` spy for controlled-mode assertions.
 * @returns {RenderResult & { onUpdateOpen: ReturnType<typeof vi.fn> }} Render result and spy.
 */
function renderDialogWithSpy() {
  const onUpdateOpen = vi.fn();

  const result = render({
    components: {
      UiDialog,
      UiDialogClose,
      UiDialogContent,
      UiDialogTrigger,
      UiButton,
    },
    template: `
      <UiDialog @update:open="onUpdateOpen">
        <UiDialogTrigger>Open</UiDialogTrigger>
        <UiDialogContent title="Dialog title" description="Dialog description">
          <p>Body</p>
          <template #actions>
            <UiDialogClose as-child>
              <UiButton type="button" variant="outline">Cancel</UiButton>
            </UiDialogClose>
          </template>
        </UiDialogContent>
      </UiDialog>
    `,
    setup() {
      return { onUpdateOpen };
    },
  });

  return { ...result, onUpdateOpen };
}

describe('UiDialog', () => {
  test('opens when trigger is clicked', async () => {
    const user = userEvent.setup();
    const { getByRole, getByText } = renderDialog();

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      expect(getByText('Dialog title')).toBeInTheDocument();
      expect(getByText('Dialog description')).toBeInTheDocument();
    });
  });

  test('is visible when open is true', async () => {
    const { getByText } = renderDialog({ open: true });

    await waitFor(() => {
      expect(getByText('Dialog title')).toBeInTheDocument();
    });
  });

  test('renders dialog role when open', async () => {
    const user = userEvent.setup();
    const { getByRole } = renderDialog();

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      expect(getByRole('dialog')).toBeInTheDocument();
    });
  });

  test('keeps content centered when open', async () => {
    const { getByRole } = renderDialog({ open: true });

    await waitFor(() => {
      expect(getByRole('dialog')).toBeInTheDocument();
    });

    const dialog = getByRole('dialog');
    expect(dialog).toHaveClass('top-[50%]');
    expect(dialog).toHaveClass('left-[50%]');
    expect(dialog).toHaveClass('translate-x-[-50%]');
    expect(dialog).toHaveClass('translate-y-[-50%]');
  });

  test('closes when Cancel is clicked', async () => {
    const user = userEvent.setup();
    const { getByRole, queryByText } = renderDialog();

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() => {
      expect(getByRole('dialog')).toBeInTheDocument();
    });

    await user.click(getByRole('button', { name: 'Cancel' }));

    await waitFor(() => {
      expect(queryByText('Dialog title')).not.toBeInTheDocument();
    });
  });

  test('closes when header close control is clicked', async () => {
    const user = userEvent.setup();
    const { getByRole, queryByText } = renderDialog();

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() => {
      expect(getByRole('button', { name: 'Close dialog' })).toBeInTheDocument();
    });

    await user.click(getByRole('button', { name: 'Close dialog' }));

    await waitFor(() => {
      expect(queryByText('Dialog title')).not.toBeInTheDocument();
    });
  });

  test('closes on Escape key press', async () => {
    const user = userEvent.setup();
    const { getByRole, queryByText } = renderDialog();

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() => {
      expect(getByRole('dialog')).toBeInTheDocument();
    });

    await user.keyboard('{Escape}');

    await waitFor(() => {
      expect(queryByText('Dialog title')).not.toBeInTheDocument();
    });
  });

  test('emits update:open when opened', async () => {
    const user = userEvent.setup();
    const { getByRole, onUpdateOpen } = renderDialogWithSpy();

    await user.click(getByRole('button', { name: 'Open' }));

    expect(onUpdateOpen).toHaveBeenCalledWith(true);
  });

  test('emits update:open when closed via Cancel', async () => {
    const user = userEvent.setup();
    const { getByRole, onUpdateOpen } = renderDialogWithSpy();

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() => {
      expect(getByRole('dialog')).toBeInTheDocument();
    });

    await user.click(getByRole('button', { name: 'Cancel' }));

    expect(onUpdateOpen).toHaveBeenCalledWith(false);
  });

  test('works with trigger as-child and UiButton', async () => {
    const user = userEvent.setup();
    const onUpdateOpen = vi.fn();

    const { getByRole, getByText } = render({
      components: {
        UiDialog,
        UiDialogClose,
        UiDialogContent,
        UiDialogTrigger,
        UiButton,
      },
      setup() {
        return { onUpdateOpen };
      },
      template: `
        <UiDialog @update:open="onUpdateOpen">
          <UiDialogTrigger as-child>
            <UiButton type="button" variant="outline">Open dialog</UiButton>
          </UiDialogTrigger>
          <UiDialogContent title="T" description="D">
            <p>Content</p>
            <template #actions>
              <UiDialogClose as-child>
                <UiButton type="button" variant="outline">Close</UiButton>
              </UiDialogClose>
            </template>
          </UiDialogContent>
        </UiDialog>
      `,
    });

    await user.click(getByRole('button', { name: 'Open dialog' }));
    expect(onUpdateOpen).toHaveBeenCalledWith(true);
    await waitFor(() => {
      expect(getByText('T')).toBeInTheDocument();
    });
  });
});

describe('UiDialogContent', () => {
  test('renders custom header slot instead of title props', async () => {
    const user = userEvent.setup();
    const { getByRole, getByText } = render({
      components: {
        UiDialog,
        UiDialogClose,
        UiDialogContent,
        UiDialogDescription,
        UiDialogHeader,
        UiDialogTitle,
        UiDialogTrigger,
        UiButton,
      },
      template: `
        <UiDialog>
          <UiDialogTrigger>Open</UiDialogTrigger>
          <UiDialogContent>
            <template #header>
              <UiDialogHeader>
                <UiDialogTitle>Custom title</UiDialogTitle>
                <UiDialogDescription>Custom description</UiDialogDescription>
              </UiDialogHeader>
            </template>
            <p>Main</p>
            <template #actions>
              <UiDialogClose as-child>
                <UiButton type="button" variant="outline">OK</UiButton>
              </UiDialogClose>
            </template>
          </UiDialogContent>
        </UiDialog>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() => {
      expect(getByText('Custom title')).toBeInTheDocument();
      expect(getByText('Custom description')).toBeInTheDocument();
    });
  });

  test('renders without actions slot', async () => {
    const user = userEvent.setup();
    const { getByRole, getByText, queryByRole } = render({
      components: {
        UiDialog,
        UiDialogContent,
        UiDialogTrigger,
      },
      template: `
        <UiDialog>
          <UiDialogTrigger>Open</UiDialogTrigger>
          <UiDialogContent title="Only title" description="Only description">
            <p>No footer actions</p>
          </UiDialogContent>
        </UiDialog>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() => {
      expect(getByText('Only title')).toBeInTheDocument();
    });
    expect(getByText('No footer actions')).toBeInTheDocument();
    expect(queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument();
    expect(getByRole('button', { name: 'Close dialog' })).toBeInTheDocument();
  });

  test('uses custom ariaLabelCloseDialog for the header close button', async () => {
    const user = userEvent.setup();
    const { getByRole, queryByRole } = render({
      components: {
        UiDialog,
        UiDialogContent,
        UiDialogTrigger,
      },
      template: `
        <UiDialog>
          <UiDialogTrigger>Open</UiDialogTrigger>
          <UiDialogContent
            title="Titled"
            description="Described"
            aria-label-close-dialog="Dismiss dialog"
          >
            <p>Body</p>
          </UiDialogContent>
        </UiDialog>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() => {
      expect(
        getByRole('button', { name: 'Dismiss dialog' }),
      ).toBeInTheDocument();
    });
    expect(
      queryByRole('button', { name: 'Close dialog' }),
    ).not.toBeInTheDocument();
  });
});
