import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { MemoryRouter, useHistoryProbe, useNavigate } from 'react-router-dom';
import type { PageLayout } from '../src/platform/types';
import { useRecordTabNavigation } from '../src/platform/features/record/useRecordTabNavigation';

// Renders the real hook against a real (stubbed) router with a real entry
// stack, so tab clicks, quick-link jumps and browser Back all go through the
// production read -> resolve -> write path rather than a source-text regex.

const layout = (over: Partial<PageLayout> = {}): PageLayout => ({
    id: 'l1', objectApiName: 'Account', tabs: ['Details', 'Activity'], sections: [], relatedLists: [],
    ...over,
} as PageLayout);

function Harness({ pageLayout }: { pageLayout: PageLayout }) {
    const nav = useRecordTabNavigation(pageLayout, []);
    const history = useHistoryProbe();
    const navigate = useNavigate();
    return (
        <div>
            <div data-testid="active">{nav.activeTab}</div>
            <div data-testid="target">{nav.scrollTarget ?? '-'}</div>
            <div data-testid="tabs">{nav.tabItems.map(t => t.id).join(',')}</div>
            <div data-testid="entries">{history.entries.join(' | ')}</div>
            <div data-testid="index">{String(history.index)}</div>
            {nav.tabItems.map(t => (
                <button key={t.id} onClick={() => nav.goToTab(t.id)}>{`go:${t.id}`}</button>
            ))}
            <button onClick={() => nav.jumpToRelatedList('contacts')}>jump:contacts</button>
            <button onClick={() => nav.jumpToRelatedList('orders')}>jump:orders</button>
            <button onClick={() => navigate(-1)}>back</button>
            <button onClick={() => navigate(1)}>forward</button>
            <button onClick={() => nav.clearScrollTarget()}>anchor-finished</button>
        </div>
    );
}

const renderAt = (url: string, pageLayout = layout()) => {
    return render(
        <MemoryRouter initialEntries={[url]}>
            <Harness pageLayout={pageLayout} />
        </MemoryRouter>,
    );
};

const active = () => screen.getByTestId('active').textContent;
const entries = () => screen.getByTestId('entries').textContent!.split(' | ');
const index = () => Number(screen.getByTestId('index').textContent);
const click = (label: string) => fireEvent.click(screen.getByText(label));
const target = () => screen.getByTestId('target').textContent;

describe('record tab navigation — URL round trip', () => {
    it('renders the tab named in the URL on first paint (deep link)', () => {
        renderAt('/object/Account/001?tab=activity');
        expect(active()).toBe('activity');
    });

    it('falls back to details when the URL names a tab this layout does not have', () => {
        renderAt('/object/Account/001?tab=nonexistent');
        expect(active()).toBe('details');
    });

    // The fallback must not write: a deep link has to survive the window before
    // the layout that defines its tab has loaded.
    it('does not rewrite the URL when falling back', () => {
        renderAt('/object/Account/001?tab=nonexistent');
        expect(entries()).toEqual(['/object/Account/001?tab=nonexistent']);
        expect(index()).toBe(0);
    });

    it('writes the clicked tab to the URL and renders it', () => {
        renderAt('/object/Account/001');
        click('go:activity');
        expect(active()).toBe('activity');
        expect(entries()[index()]).toBe('/object/Account/001?tab=activity');
    });

    // The whole point of the change.
    it('Back returns to the previous tab instead of leaving the record', () => {
        renderAt('/object/Account/001?tab=details');
        click('go:activity');
        expect(active()).toBe('activity');
        click('back');
        expect(active()).toBe('details');
        expect(entries()[index()]).toBe('/object/Account/001?tab=details');
    });

    it('Forward returns to the tab Back left', () => {
        renderAt('/object/Account/001?tab=details');
        click('go:activity');
        click('back');
        click('forward');
        expect(active()).toBe('activity');
    });

    it('preserves other query parameters when switching tabs', () => {
        renderAt('/object/Account/001?edit=true&clone=005');
        click('go:activity');
        const current = entries()[index()];
        expect(current).toContain('edit=true');
        expect(current).toContain('clone=005');
        expect(current).toContain('tab=activity');
    });
});

describe('record tab navigation — history hygiene', () => {
    it('clicking the tab you are already on adds no history entry', () => {
        renderAt('/object/Account/001?tab=activity');
        const before = entries().length;
        click('go:activity');
        click('go:activity');
        expect(entries().length).toBe(before);
        expect(active()).toBe('activity');
    });

    // Entering with no tab parameter resolves to details; clicking Details is
    // therefore a no-op that must not push a visually identical entry.
    it('clicking Details from a URL with no tab parameter adds no history entry', () => {
        renderAt('/object/Account/001');
        expect(active()).toBe('details');
        click('go:details');
        expect(entries()).toEqual(['/object/Account/001']);
    });

    it('one Back leaves the record after any number of no-op clicks', () => {
        renderAt('/object/Account/001?tab=details');
        click('go:activity');
        click('go:activity');
        click('go:activity');
        click('back');
        expect(active()).toBe('details');
    });
});

describe('record tab navigation — quick links are intra-page jumps', () => {
    const withRelated = layout({ tabs: ['Details'], relatedLists: [{ id: 'contacts', label: 'Contacts' }] } as Partial<PageLayout>);

    it('derives a Related tab so the quick-link destination is selectable', () => {
        renderAt('/object/Account/001', withRelated);
        expect(screen.getByTestId('tabs').textContent).toBe('details,related');
    });

    it('moves to the Related tab and exposes the scroll target', () => {
        renderAt('/object/Account/001', withRelated);
        click('jump:contacts');
        expect(active()).toBe('related');
        expect(target()).toBe('contacts');
    });

    // A pushed entry could not be restored in either direction: the scroll
    // target is not in the URL, so Back would land at an arbitrary offset and
    // Forward would re-render with no target. Replacing keeps history honest.
    it('adds no history entry, however many quick links are clicked', () => {
        renderAt('/object/Account/001?tab=details', withRelated);
        click('jump:contacts');
        click('jump:orders');
        click('jump:contacts');
        expect(entries().length).toBe(1);
        expect(active()).toBe('related');
    });

    // The documented consequence of replacing: the pre-jump tab is not kept in
    // history, so the way back is the tab bar — which is exactly what the
    // derived Related tab makes visible. Clicking a tab IS a destination
    // change, so it pushes and Back then works normally.
    it('the tab bar is the way back from a jump, and that move is itself undoable', () => {
        renderAt('/object/Account/001?tab=details', withRelated);
        click('jump:contacts');
        expect(active()).toBe('related');

        click('go:details');
        expect(active()).toBe('details');
        expect(entries().length).toBe(2);

        click('back');
        expect(active()).toBe('related');
    });
});

describe('record tab navigation — tab set ownership', () => {
    it('shows no Related tab when the layout configures no related lists', () => {
        renderAt('/object/Account/001', layout({ tabs: ['Details'] }));
        expect(screen.getByTestId('tabs').textContent).toBe('details');
    });

    // The old caller-side `layout.tabs || ['Details','Related']` fallback made
    // this case disagree with the one above for no reason the user could see.
    it('shows no Related tab when the layout declares no tabs and has no related lists', () => {
        renderAt('/object/Account/001', layout({ tabs: [] }));
        expect(screen.getByTestId('tabs').textContent).toBe('details');
    });

    it('keeps a Related tab the layout authored itself, without duplicating it', () => {
        renderAt('/object/Account/001', layout({
            tabs: ['Details', 'Related'],
            relatedLists: [{ id: 'contacts', label: 'Contacts' }],
        } as Partial<PageLayout>));
        expect(screen.getByTestId('tabs').textContent).toBe('details,related');
    });
});

// The scroll target's lifetime is bounded by the view it points into. These two
// cases are mirror images, and owning the target in one place is what makes
// both hold at once — clearing it from the anchor effect's cleanup fixed the
// second and broke the first.
describe('record tab navigation — scroll target lifetime', () => {
    const withRelated = layout({
        tabs: ['Details'],
        relatedLists: [{ id: 'contacts', label: 'Contacts' }, { id: 'orders', label: 'Orders' }],
    } as Partial<PageLayout>);

    it('retargeting to another quick link keeps the NEW target', () => {
        renderAt('/object/Account/001', withRelated);
        click('jump:contacts');
        expect(target()).toBe('contacts');
        click('jump:orders');
        expect(active()).toBe('related');
        expect(target()).toBe('orders');
    });

    it('leaving the related view clears a target stranded mid-anchor', () => {
        renderAt('/object/Account/001', withRelated);
        click('jump:contacts');
        expect(target()).toBe('contacts');
        click('go:details');
        expect(target()).toBe('-');
    });

    it('does not resurrect a stale target when the related view is revisited', () => {
        renderAt('/object/Account/001', withRelated);
        click('jump:contacts');
        click('go:details');
        click('go:related');
        expect(active()).toBe('related');
        expect(target()).toBe('-');
    });

    it('clears the target when the anchor reports completion', () => {
        renderAt('/object/Account/001', withRelated);
        click('jump:contacts');
        click('anchor-finished');
        expect(target()).toBe('-');
        expect(active()).toBe('related');
    });
});
