import React from 'react';
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import type { PageLayout, SObjectDescribe } from '../src/platform/types';

const load = vi.hoisted(() => vi.fn());
vi.mock('../src/platform/infrastructure/api/metadata', () => ({ metadataAPI: { getRelatedListCandidates: load } }));
import { RelatedListEditor } from '../src/platform/features/admin/components/layout/RelatedListEditor';

const layout = { id: 'layout', objectApiName: 'Parent', relatedLists: [], sections: [] } as unknown as PageLayout;
const metadata = (name: string) => ({ name, fields: [] }) as unknown as SObjectDescribe;
const editor = (name = 'Parent') => <RelatedListEditor layout={layout} metadata={metadata(name)} onUpdateLayout={vi.fn()} onRemoveRelatedList={vi.fn()} />;

beforeEach(() => load.mockReset());
afterEach(cleanup);

it('shows a failed request and retries without calling it an empty result', async () => {
    load.mockRejectedValueOnce(new Error('unavailable')).mockResolvedValueOnce([]);
    render(editor());
    expect(await screen.findByRole('alert')).toHaveTextContent('Could not load');
    expect(screen.queryByText('No related lists are available for this object.')).toBeNull();
    fireEvent.click(screen.getByRole('button', { name: 'Retry' }));
    expect(await screen.findByText('No related lists are available for this object.')).toBeTruthy();
    expect(screen.queryByRole('alert')).toBeNull();
    expect(load).toHaveBeenCalledTimes(2);
});

it('does not show candidates from a request for the previous object', async () => {
    let finishOld!: (value: unknown[]) => void;
    load.mockImplementationOnce(() => new Promise(resolve => { finishOld = resolve; })).mockResolvedValueOnce([]);
    const view = render(editor());
    expect(screen.getByRole('status')).toHaveTextContent('Loading');
    view.rerender(editor('NextParent'));
    await screen.findByText('No related lists are available for this object.');
    await act(async () => finishOld([{ id: 'Child-ParentId', label: 'Stale children', bucket: 'additional', fields: [] }]));
    await waitFor(() => expect(load).toHaveBeenCalledTimes(2));
    expect(screen.queryByText('Stale children')).toBeNull();
});

it('preserves a selected candidate through neutral save encoding and editor reload', async () => {
    const { editorToLayoutDocument, layoutDocumentToEditor } = await import('../src/platform/infrastructure/api/layoutAuthoringDocument');
    load.mockResolvedValue([{
        id: 'Child-ParentId', objectApiName: 'Child', lookupField: 'ParentId',
        label: 'Children', secondaryLabel: 'via Parent', fieldLabel: 'Parent',
        fields: ['Name', 'CreatedDate'], bucket: 'recommended', isRecommended: true, isSystem: false,
    }]);
    function Harness() {
        const [draft, setDraft] = React.useState(layout);
        return <>
            <RelatedListEditor layout={draft} metadata={metadata('Parent')} onUpdateLayout={setDraft} onRemoveRelatedList={vi.fn()} />
            <button onClick={() => setDraft(layoutDocumentToEditor(editorToLayoutDocument(draft)))}>Save and reload</button>
            <output data-testid="saved-layout">{JSON.stringify(editorToLayoutDocument(draft))}</output>
        </>;
    }
    render(<Harness />);
    fireEvent.click(await screen.findByRole('button', { name: 'Children via Parent' }));
    const expected = [{
        related_list_id: 'Child-ParentId', object_id: 'Child', lookup_field_id: 'ParentId',
        label: 'Children', secondary_label: 'via Parent', field_ids: ['Name', 'CreatedDate'], prefill_mappings: [],
    }];
    expect(JSON.parse(screen.getByTestId('saved-layout').textContent!).related_lists).toEqual(expected);
    fireEvent.click(screen.getByRole('button', { name: 'Save and reload' }));
    expect(JSON.parse(screen.getByTestId('saved-layout').textContent!).related_lists).toEqual(expected);
    expect(screen.getByRole('button', { name: 'Remove' })).toBeTruthy();
    expect(screen.queryByRole('button', { name: 'Children via Parent' })).toBeNull();
});
