/**
 * integration-card-ui regression tests.
 *
 * Covers:
 *   - Default props / structural skeleton
 *   - Button label + variant computed from `status`
 *   - Coming-soon hides the button entirely
 *   - Each action click dispatches the typed event with `detail.provider`
 *   - `[disabled]` suppresses button activation
 *   - Error message visible only when status="error" AND error-message is set
 *   - Logo prop accepts URL (renders <img>) and icon name (renders <icon-ui>)
 *   - ARIA: role="group" + aria-labelledby pointing at the provider name
 *   - aria-pressed reflects connected vs available toggle semantics
 */

import { describe, it, expect, beforeEach } from 'vitest';
import '../../core/element.js';
import '../button/button.js';
import '../badge/badge.js';
import '../icon/icon.js';
import './integration-card.js';

const tick = () => new Promise((r) => queueMicrotask(r));
const tick2 = async () => { await tick(); await tick(); };

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

describe('integration-card-ui — defaults', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  it('defaults status to "available"', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack"></integration-card-ui>');
    await tick2();
    // The reactive property accessor always returns the default; the
    // attribute is reflected only on subsequent `set` calls (UIElement
    // semantics). Both consumers + computed-button-label work off the
    // property, not the attribute.
    expect(c.status).toBe('available');
  });

  it('exposes role="group" + aria-labelledby pointing at the heading', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack"></integration-card-ui>');
    await tick2();
    expect(c.getAttribute('role')).toBe('group');
    const labelledBy = c.getAttribute('aria-labelledby');
    expect(labelledBy).toBeTruthy();
    const heading = c.querySelector(`#${labelledBy}`);
    expect(heading).not.toBeNull();
    expect(heading.textContent).toBe('Slack');
  });

  it('renders provider name into the heading', async () => {
    const c = mount('<integration-card-ui provider="github" name="GitHub"></integration-card-ui>');
    await tick2();
    const heading = c.querySelector('[data-integration-card-heading]');
    expect(heading).not.toBeNull();
    expect(heading.textContent).toBe('GitHub');
  });

  it('renders the description prop into the description element', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack" description="Send messages."></integration-card-ui>');
    await tick2();
    const desc = c.querySelector('[data-integration-card-description]');
    expect(desc).not.toBeNull();
    expect(desc.textContent).toBe('Send messages.');
    expect(desc.hidden).toBe(false);
  });
});

describe('integration-card-ui — status drives button label + variant', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  it('status="available" → primary "Connect" button', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack" status="available"></integration-card-ui>');
    await tick2();
    const btn = c.querySelector('[data-integration-card-action]');
    expect(btn).not.toBeNull();
    expect(btn.getAttribute('text')).toBe('Connect');
    expect(btn.getAttribute('variant')).toBe('primary');
    expect(btn.getAttribute('aria-pressed')).toBe('false');
  });

  it('status="connected" → outline "Configure" button with aria-pressed="true"', async () => {
    const c = mount('<integration-card-ui provider="github" name="GitHub" status="connected"></integration-card-ui>');
    await tick2();
    const btn = c.querySelector('[data-integration-card-action]');
    expect(btn).not.toBeNull();
    expect(btn.getAttribute('text')).toBe('Configure');
    expect(btn.getAttribute('variant')).toBe('outline');
    expect(btn.getAttribute('aria-pressed')).toBe('true');
  });

  it('status="error" → outline "Retry" button', async () => {
    const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error"></integration-card-ui>');
    await tick2();
    const btn = c.querySelector('[data-integration-card-action]');
    expect(btn).not.toBeNull();
    expect(btn.getAttribute('text')).toBe('Retry');
    expect(btn.getAttribute('variant')).toBe('outline');
  });

  it('status="pending" → button shows pending label and is disabled', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack" status="pending"></integration-card-ui>');
    await tick2();
    const btn = c.querySelector('[data-integration-card-action]');
    expect(btn).not.toBeNull();
    expect(btn.hasAttribute('disabled')).toBe(true);
  });

  it('status="coming-soon" → button is removed', async () => {
    const c = mount('<integration-card-ui provider="zapier" name="Zapier" status="coming-soon"></integration-card-ui>');
    await tick2();
    const btn = c.querySelector('[data-integration-card-action]');
    expect(btn).toBeNull();
  });
});

describe('integration-card-ui — status badge', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  it('status="connected" → success badge', async () => {
    const c = mount('<integration-card-ui provider="github" name="GitHub" status="connected"></integration-card-ui>');
    await tick2();
    const badge = c.querySelector('[data-integration-card-status]');
    expect(badge).not.toBeNull();
    expect(badge.hidden).toBe(false);
    expect(badge.getAttribute('variant')).toBe('success');
    expect(badge.getAttribute('text')).toBe('Connected');
  });

  it('status="error" → danger badge', async () => {
    const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error"></integration-card-ui>');
    await tick2();
    const badge = c.querySelector('[data-integration-card-status]');
    expect(badge.hidden).toBe(false);
    expect(badge.getAttribute('variant')).toBe('danger');
    expect(badge.getAttribute('text')).toBe('Error');
  });

  it('status="available" → no visible status badge', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack" status="available"></integration-card-ui>');
    await tick2();
    const badge = c.querySelector('[data-integration-card-status]');
    expect(badge.hidden).toBe(true);
  });
});

describe('integration-card-ui — events', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  it('dispatches "connect" event with provider when available button is pressed', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack" status="available"></integration-card-ui>');
    await tick2();
    let received = null;
    c.addEventListener('connect', (e) => { received = e.detail; });
    const btn = c.querySelector('[data-integration-card-action]');
    btn.dispatchEvent(new Event('press', { bubbles: true }));
    expect(received).toEqual({ provider: 'slack' });
  });

  it('dispatches "configure" event when connected button is pressed', async () => {
    const c = mount('<integration-card-ui provider="github" name="GitHub" status="connected"></integration-card-ui>');
    await tick2();
    let received = null;
    c.addEventListener('configure', (e) => { received = e.detail; });
    const btn = c.querySelector('[data-integration-card-action]');
    btn.dispatchEvent(new Event('press', { bubbles: true }));
    expect(received).toEqual({ provider: 'github' });
  });

  it('dispatches "retry" event when error button is pressed', async () => {
    const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error"></integration-card-ui>');
    await tick2();
    let received = null;
    c.addEventListener('retry', (e) => { received = e.detail; });
    const btn = c.querySelector('[data-integration-card-action]');
    btn.dispatchEvent(new Event('press', { bubbles: true }));
    expect(received).toEqual({ provider: 'stripe' });
  });

  it('[disabled] suppresses button activation', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack" status="available" disabled></integration-card-ui>');
    await tick2();
    let received = null;
    c.addEventListener('connect', (e) => { received = e.detail; });
    const btn = c.querySelector('[data-integration-card-action]');
    expect(btn.hasAttribute('disabled')).toBe(true);
    btn.dispatchEvent(new Event('press', { bubbles: true }));
    expect(received).toBeNull();
  });

  it('pending status does not dispatch any event on press', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack" status="pending"></integration-card-ui>');
    await tick2();
    let received = null;
    c.addEventListener('connect', (e) => { received = e.detail; });
    c.addEventListener('configure', (e) => { received = e.detail; });
    c.addEventListener('retry', (e) => { received = e.detail; });
    const btn = c.querySelector('[data-integration-card-action]');
    btn.dispatchEvent(new Event('press', { bubbles: true }));
    expect(received).toBeNull();
  });
});

describe('integration-card-ui — error message', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  it('renders error-message when status="error"', async () => {
    const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error" error-message="Token expired"></integration-card-ui>');
    await tick2();
    const err = c.querySelector('[data-integration-card-error]');
    expect(err).not.toBeNull();
    expect(err.hidden).toBe(false);
    expect(err.textContent).toBe('Token expired');
  });

  it('hides error message when status is not error', async () => {
    const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="available" error-message="Stale"></integration-card-ui>');
    await tick2();
    const err = c.querySelector('[data-integration-card-error]');
    if (err) expect(err.hidden).toBe(true);
  });

  it('does not render error block when status="error" but error-message empty', async () => {
    const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error"></integration-card-ui>');
    await tick2();
    const err = c.querySelector('[data-integration-card-error]');
    // Either the element is absent or hidden.
    if (err) expect(err.hidden).toBe(true);
  });
});

describe('integration-card-ui — logo rendering', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  it('renders <img> when logo prop contains a "/"', async () => {
    const c = mount('<integration-card-ui provider="slack" name="Slack" logo="/integrations/slack.svg"></integration-card-ui>');
    await tick2();
    const img = c.querySelector('[data-integration-card-logo] img');
    expect(img).not.toBeNull();
    expect(img.getAttribute('src')).toBe('/integrations/slack.svg');
    expect(img.getAttribute('alt')).toContain('Slack');
    expect(img.hasAttribute('data-integration-logo')).toBe(true);
  });

  it('renders <icon-ui> when logo prop is a bare icon name', async () => {
    const c = mount('<integration-card-ui provider="zap" name="Zap" logo="lightning"></integration-card-ui>');
    await tick2();
    const icon = c.querySelector('[data-integration-card-logo] icon-ui');
    expect(icon).not.toBeNull();
    expect(icon.getAttribute('name')).toBe('lightning');
  });

  it('renders a provider fallback glyph when logo prop is empty', async () => {
    // Empty [logo] + a [provider] → the per-provider Phosphor glyph fallback
    // (instead of a blank logo disc): wrapper stays visible with an <icon-ui>.
    const c = mount('<integration-card-ui provider="slack" name="Slack"></integration-card-ui>');
    await tick2();
    const wrap = c.querySelector('[data-integration-card-logo]');
    expect(wrap).not.toBeNull();
    expect(wrap.hidden).toBe(false);
    expect(wrap.querySelector('icon-ui')).not.toBeNull();
  });

  it('hides logo wrapper when both logo and provider are empty', async () => {
    const c = mount('<integration-card-ui name="Custom"></integration-card-ui>');
    await tick2();
    const wrap = c.querySelector('[data-integration-card-logo]');
    expect(wrap).not.toBeNull();
    expect(wrap.hidden).toBe(true);
  });
});

describe('integration-card-ui — CSS contract (source-grep)', () => {
  it('uses two-block @scope pattern (token block + base block)', async () => {
    const { readFileSync } = await import('node:fs');
    const { fileURLToPath } = await import('node:url');
    const { dirname, resolve } = await import('node:path');
    const HERE = dirname(fileURLToPath(import.meta.url));
    const CSS = readFileSync(resolve(HERE, 'integration-card.css'), 'utf8');

    expect(CSS).toMatch(/@scope\s*\(\s*integration-card-ui\s*\)/);
    expect(CSS).toMatch(/:where\(:scope\)\s*\{/);
    // Token declarations use the un-suffixed component-named tokens per component-token-contract.md
    expect(CSS).toMatch(/--integration-card-bg:/);
    expect(CSS).toMatch(/--integration-card-border:/);
    expect(CSS).toMatch(/--integration-card-radius:/);
  });

  it('keeps status-driven border tint (status="connected") inside @scope', async () => {
    const { readFileSync } = await import('node:fs');
    const { fileURLToPath } = await import('node:url');
    const { dirname, resolve } = await import('node:path');
    const HERE = dirname(fileURLToPath(import.meta.url));
    const CSS = readFileSync(resolve(HERE, 'integration-card.css'), 'utf8');
    expect(CSS).toMatch(/:scope\[status="connected"\]\s*\{[^}]*--integration-card-border/);
    expect(CSS).toMatch(/:scope\[status="error"\]\s*\{[^}]*--integration-card-border/);
  });

  it('does NOT carry raw hex / rgb / oklch values', async () => {
    const { readFileSync } = await import('node:fs');
    const { fileURLToPath } = await import('node:url');
    const { dirname, resolve } = await import('node:path');
    const HERE = dirname(fileURLToPath(import.meta.url));
    const CSS = readFileSync(resolve(HERE, 'integration-card.css'), 'utf8');
    // No #abc / #abcdef
    expect(CSS).not.toMatch(/#[0-9a-fA-F]{3,8}\b/);
    // No rgb()/rgba()
    expect(CSS).not.toMatch(/\brgba?\s*\(/);
    // No raw oklch()
    expect(CSS).not.toMatch(/\boklch\s*\(/);
  });
});
