/**
 * display-field-ui — display-only labeled value (upstream finding F5).
 *
 * Covers the contract that makes it NOT a control: group-label a11y, the
 * dual value path (attr vs foreign slot), and the optional icon/hint that
 * are removed (not just hidden) when their attribute clears.
 */
import { describe, it, expect, beforeEach } from 'vitest';
import '../../core/element.js';
import './display-field.js';
import '../icon/icon.js';

const raf = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));

function mount(html) {
  const wrap = document.createElement('div');
  wrap.innerHTML = html;
  document.body.appendChild(wrap);
  return wrap.firstElementChild;
}

describe('display-field-ui', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  it('renders as a labeled group: role=group + aria-labelledby → the label element', async () => {
    const el = mount('<display-field-ui label="Account ID" value="acct_1Q9xK2"></display-field-ui>');
    await raf();
    expect(el.getAttribute('role')).toBe('group');
    const labelEl = el.querySelector(':scope > [slot="label"]');
    expect(labelEl).not.toBeNull();
    expect(labelEl.textContent).toBe('Account ID');
    expect(labelEl.id).toBeTruthy();
    expect(el.getAttribute('aria-labelledby')).toBe(labelEl.id);
  });

  it('carries no input/focus/form semantics (no contenteditable, not focusable, no tabindex)', async () => {
    const el = mount('<display-field-ui label="Card" value="•••• 4242"></display-field-ui>');
    await raf();
    expect(el.getAttribute('contenteditable')).toBeNull();
    expect(el.getAttribute('tabindex')).toBeNull();
    const valueEl = el.querySelector(':scope > [slot="value"]');
    expect(valueEl.getAttribute('contenteditable')).toBeNull();
    expect(valueEl.textContent).toBe('•••• 4242');
  });

  it('variant="masked" reflects and the value text is preserved', async () => {
    const el = mount('<display-field-ui label="Card" value="•••• 4242" variant="masked"></display-field-ui>');
    await raf();
    expect(el.getAttribute('variant')).toBe('masked');
    expect(el.querySelector(':scope > [slot="value"]').textContent).toBe('•••• 4242');
  });

  it('a foreign default-slot child owns the value region — the attr value element is suppressed', async () => {
    const el = mount('<display-field-ui label="Card number"><div id="mount">[stripe]</div></display-field-ui>');
    await raf();
    // No auto [slot="value"] element — the foreign child is the value.
    expect(el.querySelector(':scope > [slot="value"]')).toBeNull();
    const foreign = el.querySelector(':scope > #mount');
    expect(foreign).not.toBeNull();
    expect(foreign.getAttribute('slot')).toBeNull();   // genuinely unnamed
  });

  it('icon attribute creates a leading icon-ui; clearing it removes the element (no phantom column)', async () => {
    const el = mount('<display-field-ui label="Card" value="•••• 4242" icon="lock"></display-field-ui>');
    await raf();
    const iconEl = el.querySelector(':scope > icon-ui[slot="icon"]');
    expect(iconEl).not.toBeNull();
    expect(iconEl.getAttribute('name')).toBe('lock');

    el.icon = '';
    await raf();
    expect(el.querySelector(':scope > [slot="icon"]')).toBeNull();
  });

  it('hint attribute creates a hint element + aria-describedby; clearing it removes both', async () => {
    const el = mount('<display-field-ui label="Card" value="•••• 4242" hint="Expires 12/26"></display-field-ui>');
    await raf();
    const hintEl = el.querySelector(':scope > [slot="hint"]');
    expect(hintEl).not.toBeNull();
    expect(hintEl.textContent).toBe('Expires 12/26');
    expect(el.getAttribute('aria-describedby')).toBe(hintEl.id);

    el.hint = '';
    await raf();
    expect(el.querySelector(':scope > [slot="hint"]')).toBeNull();
    expect(el.getAttribute('aria-describedby')).toBeNull();
  });

  it('surviving a DOM move (disconnect→reconnect) does NOT duplicate slot children', async () => {
    const host1 = mount('<div></div>');
    const el = document.createElement('display-field-ui');
    el.setAttribute('label', 'Card');
    el.setAttribute('value', '•••• 4242');
    el.setAttribute('hint', 'Expires 12/26');
    el.setAttribute('icon', 'lock');
    host1.appendChild(el);
    await raf();
    expect(el.querySelectorAll(':scope > [slot="value"]').length).toBe(1);
    expect(el.querySelectorAll(':scope > [slot="hint"]').length).toBe(1);
    expect(el.querySelectorAll(':scope > icon-ui[slot="icon"]').length).toBe(1);

    // Move to a new parent — fires disconnectedCallback then connectedCallback,
    // exactly what UIElement.reconcile's insertBefore does on a keyed reorder.
    const host2 = mount('<div></div>');
    host2.appendChild(el);
    await raf();

    // No duplicates after the move — connected() re-binds the existing nodes.
    expect(el.querySelectorAll(':scope > [slot="label"]').length).toBe(1);
    expect(el.querySelectorAll(':scope > [slot="value"]').length).toBe(1);
    expect(el.querySelectorAll(':scope > [slot="hint"]').length).toBe(1);
    expect(el.querySelectorAll(':scope > icon-ui[slot="icon"]').length).toBe(1);
    expect(el.querySelector(':scope > [slot="value"]').textContent).toBe('•••• 4242');
  });
});
