/**
 * AdiaUI Signals — Reactive primitives.
 *
 * Zero dependencies. No DOM required.
 * Use standalone for state management in any JS context.
 *
 *   import { signal, computed, effect, batch } from './signals.js';
 *
 *   const count = signal(0);
 *   const double = computed(() => count.value * 2);
 *   effect(() => console.log(double.value));
 *   count.value = 5; // logs: 10
 */

const SIGNAL = Symbol('signal');

let tracking = null;
let pending = null;
let batching = false;

function notify(subs) {
  for (const s of subs) {
    if (s.disposed) { subs.delete(s); continue; }
    s.dirty = true;
    if (!pending) { pending = new Set(); if (!batching) queueMicrotask(flushSigs); }
    pending.add(s);
  }
}

function flushSigs() {
  let q, loops = 0;
  while ((q = pending)) {
    if (++loops > 100) {
      pending = null;
      console.error(`signals: drain loop exceeded 100 iterations`);
      break;
    }
    pending = null;
    for (const s of q) {
      if (!s.disposed && s.dirty) {
        s.dirty = false;
        try { s.run(); }
        catch (e) {
          if (s.onError) { s.onError(e); }
          else { s.disposed = true; queueMicrotask(() => { throw e; }); }
        }
      }
    }
  }
}

export function batch(fn) {
  if (batching) { fn(); return; }
  batching = true;
  try { fn(); }
  finally { batching = false; if (pending) flushSigs(); }
}

export function untracked(fn) {
  const prev = tracking;
  tracking = null;
  try { return fn(); }
  finally { tracking = prev; }
}

export function signal(v) {
  const subs = new Set();
  return {
    [SIGNAL]: true,
    get value() { if (tracking) subs.add(tracking); return v; },
    set value(n) { if (Object.is(v, n)) return; v = n; notify(subs); },
    peek() { return v; },
  };
}

export function computed(fn) {
  const subs = new Set();
  let cached, dirty = true;
  const sub = {
    dirty: false, disposed: false,
    run() { dirty = true; notify(subs); },
  };
  return {
    [SIGNAL]: true,
    get value() {
      if (tracking) subs.add(tracking);
      if (dirty) {
        const prev = tracking;
        tracking = sub;
        try { cached = fn(); dirty = false; } finally { tracking = prev; }
      }
      return cached;
    },
    peek() { return cached; },
  };
}

export function effect(fn, opts) {
  const sub = {
    dirty: false, disposed: false,
    host: opts?.host ?? null,
    onError: opts?.onError ?? null,
    run() { const p = tracking; tracking = sub; try { fn(); } finally { tracking = p; } },
  };
  sub.run();
  return () => { sub.disposed = true; };
}

export function isSignal(v) {
  return v != null && typeof v === 'object' && v[SIGNAL] === true;
}

export { SIGNAL };
