import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import { foregroundOnAccentRest } from '@genesislcap/foundation-ui';
import { styles } from './main.styles';

// CSS is not otherwise unit-tested in this package, so this covers the two
// properties of the BLOCKED BANNER that are cheap to assert from the generated
// text and that a reviewer had to catch by eye:
//
//   1. Design-system tokens are consumed UNFALLBACKED, as every other rule in the
//      file does. A `var(--token, fallback)` here is dead code at best — both
//      tokens below ship with defaults — and actively wrong at worst: the blocked
//      banner's old `var(--neutral-layer-2, transparent)` would, if it ever fired,
//      have produced a banner with no surface at all.
//   2. The blocked banner's visual treatment hangs off `.is-visible`, not the bare
//      class, because the container stays mounted while unblocked so that its
//      `role="status"` region is registered before the text arrives (see
//      main.template.ts). Collapsing the empty container would undo that.
//
// Scope: the banner's own rules, and nothing else. Both negative assertions were
// written for one component, but an earlier version grepped the WHOLE stylesheet
// — quietly turning a banner test into a global style policy nobody agreed to, so
// any future rule anywhere in an 800-line sheet that legitimately wanted a
// fallback on either token would have failed a test named after the banner.
//
// A second suite at the bottom covers the user bubble's fill/foreground token
// pairing, which failed the same way a reviewer's eye does (GENC-1491).

const suite = createLogicSuite('main.styles blocked-banner rules');

/**
 * The generated stylesheet text. `css` composes nested `ElementStyles` (this file
 * builds the sheet from several partials), so flatten recursively rather than
 * only reading the top level — a shallow read silently returns almost nothing,
 * which would make every `assert.not.ok` below pass vacuously. The first test
 * guards exactly that.
 */
const flatten = (value: unknown): string => {
  if (typeof value === 'string') return value;
  if (Array.isArray(value)) return value.map(flatten).join('\n');
  const nested = (value as { styles?: unknown } | null | undefined)?.styles;
  return nested ? flatten(nested) : '';
};

const cssText = flatten(styles);

/**
 * Every declaration block whose selector mentions `.blocked-banner`, joined.
 *
 * Deliberately crude — one regex over the generated text, no CSS parser — but
 * enough to keep these assertions about the banner rather than about the whole
 * sheet, which was the only thing wrong with them. It matches
 * `<selector> { <declarations> }` for any selector containing `.blocked-banner`,
 * so it picks up the bare class, the `.is-visible` / `.is-partial` modifiers, the
 * shared composer-width group and the copy inside the container query alike.
 */
const bannerRules = (cssText.match(/[^{}]*\.blocked-banner[^{}]*\{[^}]*\}/g) ?? []).join('\n');

/** Declarations of the bare-class rule — the one that applies while empty. */
const emptyStateRule = cssText.match(/[\s{}](\.blocked-banner)\s*\{([^}]*)\}/)?.[2] ?? '';

suite('the banner rules were extracted as text (guards the assertions below)', () => {
  assert.ok(cssText.includes('.blocked-banner'), 'the banner rules are in the sheet');
  assert.ok(bannerRules.includes('.is-visible'), 'the extraction found the modifier rules');
  assert.ok(emptyStateRule.length > 0, 'and isolated the bare-class rule');
});

suite('the banner consumes design-system tokens without dead fallbacks', () => {
  assert.not.ok(
    bannerRules.includes('var(--neutral-layer-2,'),
    '--neutral-layer-2 has a DS default and is used unfallbacked everywhere else here',
  );
  assert.not.ok(
    bannerRules.includes('var(--warning-color,'),
    '--warning-color has a DS default and is used unfallbacked by toast/badge styles',
  );
});

suite('the blocked banner keeps its unblocked container visually inert', () => {
  // `::part(blocked-banner)` matches at all times, so the empty state must set
  // nothing that reserves space — and must not remove the container from the
  // accessibility tree, which is where the `role="status"` region is registered.
  //
  // Asserted against the bare-class rule's own declarations rather than by
  // grepping for a `:empty` selector nobody wrote: this catches the mistake as it
  // would actually be made — a padding or border creeping onto the always-present
  // container — instead of one specific spelling of it.
  //
  // `margin-block` rather than `margin`: the shared composer-width group sets
  // `margin-inline: auto` on the banner along with the rest of the composer
  // column, which centres it and reserves nothing.
  assert.ok(cssText.includes('.blocked-banner.is-visible'), 'visuals hang off is-visible');
  for (const property of ['display: none', 'padding', 'border', 'background', 'margin-block']) {
    assert.not.ok(
      emptyStateRule.includes(property),
      `the always-present container must not set ${property} — that is .is-visible's job`,
    );
  }
});

suite('the visible banner hands its end margin to the resize handle', () => {
  // Reviewer's manual-testing note: the banner's block-end margin plus the 7px
  // handle stacked into a gap the hover pill was NOT centred in (it centres in
  // the handle alone). The adjacency rule collapses the two so the handle IS
  // the gap. What gets pinned is the INVARIANT, not the selector's existence:
  // both rules must read the ONE shared token, because the cancellation only
  // works while the banner's margin and the handle's pull are equal-and-
  // opposite — two hand-kept magnitudes ~80 lines apart regress silently, and
  // an existence-only assertion stayed green while they did.
  assert.ok(
    /:host\s*\{[^}]*--blocked-banner-gap:/.test(cssText),
    'the shared gap token is declared on the host (a sibling cannot read one on the banner)',
  );
  assert.ok(
    /\.blocked-banner\.is-visible\s*\{[^}]*margin-block:\s*var\(--blocked-banner-gap\)/.test(
      cssText,
    ),
    "the banner's block margin IS the token",
  );
  assert.ok(
    /\.blocked-banner\.is-visible \+ \.composer-resize-handle\s*\{[^}]*margin-block-start:\s*calc\(var\(--blocked-banner-gap\) \* -1\)/.test(
      cssText,
    ),
    'and the handle pulls up by exactly that token, negated — equal and opposite by construction',
  );
});

suite('partial exhaustion has its own modifier, layered on the visible state', () => {
  // `.is-visible` carries the layout and surface for BOTH states; `.is-partial`
  // only softens it. Additive, never a replacement — so host CSS keyed on the
  // visible state needs to know nothing about partial exhaustion.
  assert.ok(cssText.includes('.blocked-banner.is-partial'), 'the partial modifier exists');
});

suite.run();

// ─── User message bubble ──────────────────────────────────────────────────────

const bubble = createLogicSuite('main.styles user-message colour pairing');

/** Declarations of the `.message.user` rule — the accent-filled bubble. */
const userMessageRule = cssText.match(/\.message\.user\s*\{([^}]*)\}/)?.[1] ?? '';

bubble('the user bubble pairs its accent fill with the on-accent foreground', () => {
  // The bubble fills with --accent-fill-rest, so its text MUST come from the
  // design system's paired on-accent token. This shipped as
  // `--accent-foreground-on-accent-rest` — a name nothing defines — and the
  // failure was invisible in review and in dark mode: `color` is inherited, so an
  // undefined custom property with no fallback is invalid at computed-value time
  // and quietly leaves the text at the inherited neutral foreground. That reads
  // white-ish on a dark app (looks intentional) and near-black on a light one,
  // where it landed at ~1.3:1 against the fill (GENC-1491).
  //
  // Asserted against the token object's OWN cssCustomProperty rather than a
  // repeated string literal: a typo here cannot agree with itself, and a rename
  // in the design system fails this test instead of silently reintroducing the
  // inherit.
  assert.ok(userMessageRule.length > 0, 'the .message.user rule is in the sheet');
  assert.ok(
    userMessageRule.includes('var(--accent-fill-rest)'),
    'the bubble is filled from the accent token (the premise of the pairing)',
  );
  assert.ok(
    userMessageRule.includes(`color: var(${foregroundOnAccentRest.cssCustomProperty})`),
    `the text colour is ${foregroundOnAccentRest.cssCustomProperty}, which the design system defines`,
  );
});

bubble.run();
