/**
 * time-picker-ui — SPEC-043
 *
 * Coverage:
 *   - Stamp shape (precision × hour-cycle)
 *   - Segment text sync from value
 *   - Arrow Up / Down increments
 *   - Page Up / Down + Home / End
 *   - Tab equivalents (ArrowLeft / ArrowRight)
 *   - Digit typing + auto-advance
 *   - Form participation (name + ISO value)
 *   - min / max → invalid event
 *   - disabled / readonly behavior
 *   - precision toggle re-stamps without losing hh/mm
 */

import { describe, it, expect, beforeEach } from 'vitest';
import '../../core/element.js';
import './time-picker.js';

const tick = () => new Promise((r) => queueMicrotask(r));

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

function segText(el, key) {
  return el.querySelector(`[data-segment="${key}"]`)?.textContent || '';
}

function fireKey(el, key) {
  el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }));
}

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

  it('renders with empty segments when no value is set', async () => {
    const el = mount('<time-picker-ui></time-picker-ui>');
    await tick();
    expect(el.querySelector('[data-segment="hour"]')).not.toBeNull();
    expect(el.querySelector('[data-segment="minute"]')).not.toBeNull();
    expect(segText(el, 'hour')).toBe('');
    expect(segText(el, 'minute')).toBe('');
  });

  it('populates hour/minute segments from value="09:30"', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    expect(segText(el, 'hour')).toBe('09');
    expect(segText(el, 'minute')).toBe('30');
  });

  it('precision="second" stamps a seconds segment + separator', async () => {
    const el = mount('<time-picker-ui precision="second" value="14:30:45"></time-picker-ui>');
    await tick();
    expect(el.querySelector('[data-segment="second"]')).not.toBeNull();
    expect(segText(el, 'second')).toBe('45');
  });

  it('hour-cycle="h12" stamps a meridiem segment + 12h display', async () => {
    const el = mount('<time-picker-ui hour-cycle="h12" value="14:30"></time-picker-ui>');
    await tick();
    const meridiem = el.querySelector('[data-segment="meridiem"]');
    expect(meridiem).not.toBeNull();
    expect(segText(el, 'hour')).toBe('02');
    expect(segText(el, 'meridiem')).toBe('PM');
  });

  it('hour-cycle="h12" displays midnight as 12 AM', async () => {
    const el = mount('<time-picker-ui hour-cycle="h12" value="00:15"></time-picker-ui>');
    await tick();
    expect(segText(el, 'hour')).toBe('12');
    expect(segText(el, 'meridiem')).toBe('AM');
  });

  it('Arrow Up on hour segment increments by 1', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    const hour = el.querySelector('[data-segment="hour"]');
    hour.focus();
    fireKey(hour, 'ArrowUp');
    await tick();
    expect(el.value).toBe('10:30');
  });

  it('Arrow Down on minute segment decrements by step/60 minutes', async () => {
    const el = mount('<time-picker-ui value="09:30" step="60"></time-picker-ui>');
    await tick();
    const min = el.querySelector('[data-segment="minute"]');
    min.focus();
    fireKey(min, 'ArrowDown');
    await tick();
    expect(el.value).toBe('09:29');
  });

  it('step="900" increments minute by 15 on Arrow Up', async () => {
    const el = mount('<time-picker-ui value="09:30" step="900"></time-picker-ui>');
    await tick();
    const min = el.querySelector('[data-segment="minute"]');
    min.focus();
    fireKey(min, 'ArrowUp');
    await tick();
    expect(el.value).toBe('09:45');
  });

  it('Page Up increments by 10× the small step', async () => {
    const el = mount('<time-picker-ui value="09:00"></time-picker-ui>');
    await tick();
    const min = el.querySelector('[data-segment="minute"]');
    min.focus();
    fireKey(min, 'PageUp');
    await tick();
    expect(el.value).toBe('09:10');
  });

  it('Home jumps to segment min; End jumps to segment max', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    const hour = el.querySelector('[data-segment="hour"]');
    hour.focus();
    fireKey(hour, 'Home');
    await tick();
    expect(el.value).toBe('00:30');
    fireKey(hour, 'End');
    await tick();
    expect(el.value).toBe('23:30');
  });

  it('ArrowRight moves focus to next segment; ArrowLeft to previous', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    const hour = el.querySelector('[data-segment="hour"]');
    const minute = el.querySelector('[data-segment="minute"]');
    hour.focus();
    fireKey(hour, 'ArrowRight');
    await tick();
    expect(document.activeElement).toBe(minute);
    fireKey(minute, 'ArrowLeft');
    await tick();
    expect(document.activeElement).toBe(hour);
  });

  it('disabled prop blocks Arrow-key step', async () => {
    const el = mount('<time-picker-ui value="09:30" disabled></time-picker-ui>');
    await tick();
    const hour = el.querySelector('[data-segment="hour"]');
    fireKey(hour, 'ArrowUp');
    await tick();
    expect(el.value).toBe('09:30');
  });

  it('readonly prop blocks edits but keeps segments focusable', async () => {
    const el = mount('<time-picker-ui value="09:30" readonly></time-picker-ui>');
    await tick();
    const hour = el.querySelector('[data-segment="hour"]');
    expect(hour.getAttribute('tabindex')).toBe('0');
    expect(hour.contentEditable).toBe('false');
    hour.focus();
    fireKey(hour, 'ArrowUp');
    await tick();
    expect(el.value).toBe('09:30');
  });

  it('min violation fires invalid + does not mutate value', async () => {
    const el = mount('<time-picker-ui value="08:00" min="08:00" max="18:00"></time-picker-ui>');
    await tick();
    let captured = null;
    el.addEventListener('invalid', (e) => { captured = e; });
    const hour = el.querySelector('[data-segment="hour"]');
    hour.focus();
    fireKey(hour, 'ArrowDown');
    await tick();
    expect(captured).not.toBeNull();
    expect(captured.detail.reason).toBe('min');
    expect(el.value).toBe('08:00');
  });

  it('max violation fires invalid', async () => {
    const el = mount('<time-picker-ui value="18:00" min="08:00" max="18:00"></time-picker-ui>');
    await tick();
    let captured = null;
    el.addEventListener('invalid', (e) => { captured = e; });
    const hour = el.querySelector('[data-segment="hour"]');
    hour.focus();
    fireKey(hour, 'ArrowUp');
    await tick();
    expect(captured).not.toBeNull();
    expect(captured.detail.reason).toBe('max');
    expect(el.value).toBe('18:00');
  });

  it('emits change with detail.segment on Arrow step', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    let captured = null;
    el.addEventListener('change', (e) => { captured = e; });
    const hour = el.querySelector('[data-segment="hour"]');
    hour.focus();
    fireKey(hour, 'ArrowUp');
    await tick();
    expect(captured).not.toBeNull();
    expect(captured.detail).toEqual({ value: '10:30', segment: 'hour' });
  });

  it('precision="minute" → "second" re-stamps without losing hh/mm', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    expect(el.querySelector('[data-segment="second"]')).toBeNull();
    el.precision = 'second';
    await tick();
    expect(el.querySelector('[data-segment="second"]')).not.toBeNull();
    expect(segText(el, 'hour')).toBe('09');
    expect(segText(el, 'minute')).toBe('30');
  });

  it('aria-valuemax matches segment max (h23 → 23 for hour)', async () => {
    const el = mount('<time-picker-ui hour-cycle="h23"></time-picker-ui>');
    await tick();
    const hour = el.querySelector('[data-segment="hour"]');
    expect(hour.getAttribute('aria-valuemax')).toBe('23');
  });

  it('aria-valuemax for hour is 12 in h12 cycle', async () => {
    const el = mount('<time-picker-ui hour-cycle="h12"></time-picker-ui>');
    await tick();
    const hour = el.querySelector('[data-segment="hour"]');
    expect(hour.getAttribute('aria-valuemax')).toBe('12');
  });

  it('aria-valuenow + aria-valuetext are populated from value', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    const hour = el.querySelector('[data-segment="hour"]');
    const minute = el.querySelector('[data-segment="minute"]');
    expect(hour.getAttribute('aria-valuenow')).toBe('9');
    expect(minute.getAttribute('aria-valuenow')).toBe('30');
    expect(hour.getAttribute('aria-valuetext')).toContain('hour');
    expect(minute.getAttribute('aria-valuetext')).toContain('minute');
  });

  it('host role="group" + aria-label default', async () => {
    const el = mount('<time-picker-ui></time-picker-ui>');
    await tick();
    expect(el.getAttribute('role')).toBe('group');
    expect(el.hasAttribute('aria-label')).toBe(true);
  });

  it('clear() resets value to empty', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    el.clear();
    await tick();
    expect(el.value).toBe('');
    expect(segText(el, 'hour')).toBe('');
  });

  it('stepUp() increments programmatically', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    el.stepUp('hour');
    await tick();
    expect(el.value).toBe('10:30');
  });

  it('value setter syncs segments', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    el.value = '11:45';
    await tick();
    expect(segText(el, 'hour')).toBe('11');
    expect(segText(el, 'minute')).toBe('45');
  });

  it('focusing a segment sets [editing] + [segment="…"] on host', async () => {
    const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
    await tick();
    const minute = el.querySelector('[data-segment="minute"]');
    minute.focus();
    await tick();
    expect(el.hasAttribute('editing')).toBe(true);
    expect(el.getAttribute('segment')).toBe('minute');
  });
});
