/**
 * AdiaUI Responsive — viewport-breakpoint signal + `@bp` attribute parser.
 *
 * Components that support responsive attribute values read `breakpoint.value`
 * inside their `render()` method. UIElement wraps render() in an effect, so
 * the component automatically re-renders whenever the viewport crosses a
 * breakpoint boundary — no manual subscription needed.
 *
 * ## Syntax
 *
 *   "4"             → scalar, fast-path (no parsing)
 *   "1 2@sm 4@lg"   → 1 on xs, 2 from sm, 4 from lg/xl
 *   "4 1@sm"        → 4 on md/lg/xl, 1 on xs/sm
 *   "2@md"          → 2 from md+, inherits component default below md
 *
 * Semantics: mobile-first / min-width — `@bp` means "apply at this
 * breakpoint and all wider viewports, unless overridden by a larger
 * annotation". The unannotated value is the base (smallest screens).
 *
 * Resolution: largest `@bp` whose index ≤ active-bp-index wins.
 * If no annotation matches, the unannotated default is returned.
 *
 * @example
 *   import { parseResponsive, breakpoint } from '../../core/responsive.js';
 *
 *   render() {
 *     const bp   = breakpoint.value;              // subscribe
 *     const cols = parseResponsive(this.columns, bp);
 *     this.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
 *   }
 */

import { signal } from './signals.js';

// ─── Breakpoint scale ─────────────────────────────────────────────────────────

/** Min-width px thresholds, narrowest first. */
export const BREAKPOINTS = /** @type {Record<string, number>} */ ({
  xs:    0,
  sm:  480,
  md:  768,
  lg: 1024,
  xl: 1280,
});

/** Ordered list of breakpoint names, narrowest → widest. */
export const BP_NAMES = /** @type {string[]} */ (Object.keys(BREAKPOINTS));
const BP_VALS          = /** @type {number[]} */ (Object.values(BREAKPOINTS));

// ─── Active-breakpoint signal ─────────────────────────────────────────────────

function _detect() {
  if (typeof window === 'undefined') return 'lg'; // SSR / non-browser fallback
  const w = window.innerWidth;
  for (let i = BP_NAMES.length - 1; i >= 0; i--) {
    if (w >= BP_VALS[i]) return BP_NAMES[i];
  }
  return 'xs';
}

/**
 * Shared signal — current viewport breakpoint name.
 * Reading `.value` inside a UIElement `render()` creates a subscription;
 * the component re-renders automatically on breakpoint change.
 *
 * @type {import('./signals.js').Signal<string>}
 */
export const breakpoint = signal(_detect());

// Wire matchMedia listeners once (browser only, skipped in SSR / tests)
if (typeof window !== 'undefined') {
  // One listener per non-zero threshold; fires whenever the viewport crosses
  // that boundary in either direction.
  for (let i = 1; i < BP_VALS.length; i++) {
    window
      .matchMedia(`(min-width: ${BP_VALS[i]}px)`)
      .addEventListener('change', () => { breakpoint.value = _detect(); });
  }
}

// ─── Parser ───────────────────────────────────────────────────────────────────

/**
 * Resolve a responsive attribute value for the given breakpoint.
 *
 * @param {string | null | undefined} value    Raw attribute string.
 * @param {string}                    activeBp Current breakpoint name.
 * @returns {string | null}                    Resolved scalar, or null.
 */
export function parseResponsive(value, activeBp = breakpoint.value) {
  if (!value) return null;
  if (!value.includes('@')) return value; // fast path — plain scalar

  let defaultVal = /** @type {string | null} */ (null);
  const bpMap    = /** @type {Map<string, string>} */ (new Map());

  for (const token of value.trim().split(/\s+/)) {
    const at = token.lastIndexOf('@');
    if (at === -1) {
      defaultVal = token;
    } else {
      bpMap.set(token.slice(at + 1), token.slice(0, at));
    }
  }

  // Find the largest @bp annotation whose index ≤ active-bp index.
  const activeIdx = BP_NAMES.indexOf(activeBp);
  let   best      = /** @type {string | null} */ (null);
  let   bestIdx   = -1;

  for (const [bp, val] of bpMap) {
    const idx = BP_NAMES.indexOf(bp);
    if (idx !== -1 && idx <= activeIdx && idx > bestIdx) {
      best    = val;
      bestIdx = idx;
    }
  }

  return best ?? defaultVal;
}
