import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import { settingsModalStyles } from './settings-modal.styles';

// GENC-1464 budget meter — the styling invariants a reviewer would otherwise
// have to catch by eye, following main.styles.test.ts (see there for why CSS
// gets asserted from the generated text at all, and for the scoping lesson:
// these assertions are about the BUDGET rules only, not a global style policy).
//
//   1. Design-system tokens are consumed UNFALLBACKED — the #2411/#2436 review
//      rule. Every token the meter reads ships a DS default, so a
//      `var(--token, fallback)` is dead code at best and a silently-broken
//      token name at worst.
//   2. The meter's row anatomy is SHARED with the context indicator via grouped
//      selectors, not copied. The two indicators are siblings by design;
//      copied declarations would let them drift apart one edit at a time, which
//      is exactly what the grouping was chosen to make impossible.

const suite = createLogicSuite('settings-modal.styles budget-meter rules');

/**
 * The generated stylesheet text, flattened recursively — `css` composes nested
 * `ElementStyles`, and a shallow read can return almost nothing, making every
 * negative assertion below pass vacuously. The first test guards 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) : '';
};

/**
 * Comments stripped before matching: the assertions below are about
 * DECLARATIONS, and the sheet's own prose legitimately mentions the very
 * pattern (`var(--x, y)`) the negative assertion hunts for.
 */
const cssText = flatten(settingsModalStyles).replace(/\/\*[\s\S]*?\*\//g, '');

/** Every declaration block whose selector mentions `.settings-budget-`, joined. */
const budgetRules = (cssText.match(/[^{}]*\.settings-budget-[^{}]*\{[^}]*\}/g) ?? []).join('\n');

suite('the budget rules were extracted as text (guards the assertions below)', () => {
  assert.ok(cssText.includes('.settings-budget-usage'), 'the meter container rule is in the sheet');
  assert.ok(budgetRules.length > 0, 'the extraction found the budget rules');
});

suite('the meter consumes design-system tokens without dead fallbacks', () => {
  // One structural probe rather than a per-token list: ANY `var(--x,` inside a
  // budget rule is a fallback, and the meter reads only tokens that ship DS
  // defaults (--design-unit, --neutral-foreground-*), so there is no legitimate
  // fallback for this scope. Scoped to the budget rules on purpose — see
  // main.styles.test.ts for why a whole-sheet grep is the wrong test.
  assert.not.ok(/var\(--[a-z-]+\s*,/.test(budgetRules), 'no var(--token, fallback) in the meter');
  assert.ok(budgetRules.includes('var(--design-unit)'), 'and it does consume tokens');
});

suite('every meter row sits on one shared rhythm', () => {
  // The 4/2/2 fix. `.settings-modal-section-body` gaps at 4 units and the
  // budget meter at 2, so an unwrapped context indicator sat 4 units above the
  // FIRST vendor row while the vendor rows sat 2 apart — three visually
  // identical meter rows, unevenly spaced. Both meters now live in
  // `.settings-usage-meters`, which must carry the SAME gap as the meter it
  // wraps: asserted as ONE grouped rule, so the two cannot drift to different
  // rhythms. (settings-modal.template.test.ts pins the other half — that the
  // wrapper is conditional, so a host with no meters pays no stray gap for it.)
  const shared = /\.settings-usage-meters\s*,\s*\.settings-budget-usage\s*\{([^}]*)\}/.exec(
    cssText,
  );
  assert.ok(shared, '.settings-usage-meters is grouped with .settings-budget-usage');
  assert.ok(shared![1].includes('flex-direction: column'), 'the shared container is a column');
  assert.ok(
    shared![1].includes('gap: calc(var(--design-unit) * 2px)'),
    'gapped at the meter rhythm (2 units), not the section body 4',
  );
});

suite("the meter shares the context indicator's row anatomy by grouped selector", () => {
  // The invariant is the SHARING, not the shape: each budget class must sit in
  // the same selector list as its context sibling, so restyling one restyles
  // both and the two indicators cannot drift apart.
  const sharedPairs: Array<[string, string]> = [
    ['.settings-context-header', '.settings-budget-header'],
    ['.settings-context-label', '.settings-budget-label'],
    ['.settings-context-tokens', '.settings-budget-figures'],
  ];
  for (const [contextClass, budgetClass] of sharedPairs) {
    const grouped = new RegExp(
      `${contextClass.replace(/\./g, '\\.')}\\s*,\\s*${budgetClass.replace(/\./g, '\\.')}\\s*\\{`,
    );
    assert.ok(grouped.test(cssText), `${budgetClass} is grouped with ${contextClass}`);
  }
});

suite.run();
