'use strict';

function stripTypes(code, format) {
    if (format === 'ts')
        return code;
    return (code
        .replace(/\s+as\s+\w+(?:\[\])?/g, '')
        .replace(/\((\w+):\s*\w+\)/g, '($1)')
        .replace(/\((\w+):\s*any\)/g, '($1)')
        .replace(/Promise<void>\[\]/g, 'Promise[]')
        .replace(/Promise<void>/g, 'Promise')
        .replace(/:\s*TransitionEvent/g, ''));
}
const COMMAND_IMPLEMENTATIONS_TS = {
    toggle: `
    case 'toggle': {
      const className = getClassName(cmd.args[0]) || String(await evaluate(cmd.args[0], ctx));
      const targets = await getTarget();
      for (const el of targets) el.classList.toggle(className);
      ctx.it = targets.length === 1 ? targets[0] : targets;
      return ctx.it;
    }`,
    add: `
    case 'add': {
      const className = getClassName(cmd.args[0]) || String(await evaluate(cmd.args[0], ctx));
      const targets = await getTarget();
      for (const el of targets) el.classList.add(className);
      ctx.it = targets.length === 1 ? targets[0] : targets;
      return ctx.it;
    }`,
    remove: `
    case 'remove': {
      const targets = await getTarget();
      for (const el of targets) el.remove();
      return null;
    }`,
    removeClass: `
    case 'removeClass': {
      const className = getClassName(cmd.args[0]) || String(await evaluate(cmd.args[0], ctx));
      const targets = await getTarget();
      for (const el of targets) el.classList.remove(className);
      return targets;
    }`,
    show: `
    case 'show': {
      const targets = await getTarget();
      for (const el of targets) {
        (el as HTMLElement).style.display = '';
        el.classList.remove('hidden');
      }
      return targets;
    }`,
    hide: `
    case 'hide': {
      const targets = await getTarget();
      for (const el of targets) (el as HTMLElement).style.display = 'none';
      return targets;
    }`,
    set: `
    case 'set': {
      const target = cmd.args[0];
      const value = await evaluate(cmd.args[1], ctx);

      if (target.type === 'variable') {
        const varName = target.name.slice(1);
        const map = target.scope === 'local' ? ctx.locals : globalVars;
        map.set(varName, value);
        ctx.it = value;
        return value;
      }

      if (target.type === 'possessive' || target.type === 'member') {
        const obj = await evaluate(target.object, ctx);
        if (obj) {
          if (obj instanceof Element && isStyleProp(target.property)) {
            setStyleProp(obj, target.property, value);
          } else {
            (obj as any)[target.property] = value;
          }
          ctx.it = value;
          return value;
        }
      }

      ctx.it = value;
      return value;
    }`,
    get: `
    case 'get': {
      const value = await evaluate(cmd.args[0], ctx);
      ctx.it = value;
      return value;
    }`,
    wait: `
    case 'wait': {
      const duration = await evaluate(cmd.args[0], ctx);
      const ms = typeof duration === 'number' ? duration : parseInt(String(duration));
      await new Promise(resolve => setTimeout(resolve, ms));
      return ms;
    }`,
    transition: `
    case 'transition': {
      const property = String(await evaluate(cmd.args[0], ctx)).replace(/^\\*/, '');
      const toValue = await evaluate(cmd.args[1], ctx);
      const durationVal = await evaluate(cmd.args[2], ctx);
      const duration = typeof durationVal === 'number' ? durationVal :
                       String(durationVal).endsWith('ms') ? parseInt(String(durationVal)) :
                       String(durationVal).endsWith('s') ? parseFloat(String(durationVal)) * 1000 :
                       parseInt(String(durationVal)) || 300;

      const targets = await getTarget();
      const promises: Promise<void>[] = [];

      for (const el of targets) {
        const htmlEl = el as HTMLElement;
        const kebabProp = property.replace(/([A-Z])/g, '-$1').toLowerCase();

        const oldTransition = htmlEl.style.transition;
        htmlEl.style.transition = \`\${kebabProp} \${duration}ms ease\`;
        htmlEl.style.setProperty(kebabProp, String(toValue));

        promises.push(new Promise<void>(resolve => {
          const cleanup = () => {
            htmlEl.style.transition = oldTransition;
            resolve();
          };

          const onEnd = (e: TransitionEvent) => {
            if (e.propertyName === kebabProp) {
              htmlEl.removeEventListener('transitionend', onEnd);
              cleanup();
            }
          };

          htmlEl.addEventListener('transitionend', onEnd);
          setTimeout(cleanup, duration + 50);
        }));
      }

      await Promise.all(promises);
      ctx.it = targets.length === 1 ? targets[0] : targets;
      return ctx.it;
    }`,
    go: `
    case 'go': {
      const dest = await evaluate(cmd.args[0], ctx);
      const d = String(dest).toLowerCase();
      if (d === 'back') history.back();
      else if (d === 'forward') history.forward();
      else if (d === 'bottom') ctx.me.scrollIntoView({ block: 'end', behavior: 'smooth' });
      else if (d === 'top') ctx.me.scrollIntoView({ block: 'start', behavior: 'smooth' });
      else window.location.href = String(dest);
      return null;
    }`,
    call: `
    case 'call': {
      const result = await evaluate(cmd.args[0], ctx);
      ctx.it = result;
      return result;
    }`,
    log: `
    case 'log': {
      const values = await Promise.all(cmd.args.map((a: any) => evaluate(a, ctx)));
      console.log(...values);
      return values[0];
    }`,
    send: `
    case 'send': {
      const eventName = await evaluate(cmd.args[0], ctx);
      const targets = await getTarget();
      const event = new CustomEvent(String(eventName), { bubbles: true, detail: ctx.it });
      for (const el of targets) el.dispatchEvent(event);
      ctx.it = event;
      return event;
    }`,
    trigger: `
    case 'trigger': {
      const eventName = await evaluate(cmd.args[0], ctx);
      const targets = await getTarget();
      const event = new CustomEvent(String(eventName), { bubbles: true, detail: ctx.it });
      for (const el of targets) el.dispatchEvent(event);
      ctx.it = event;
      return event;
    }`,
    put: `
    case 'put': {
      const content = await evaluate(cmd.args[0], ctx);
      const modifier = cmd.modifier || 'into';

      if (cmd.target?.type === 'possessive' && isStyleProp(cmd.target.property)) {
        const obj = await evaluate(cmd.target.object, ctx);
        const elements = toElementArray(obj);
        for (const el of elements) {
          setStyleProp(el, cmd.target.property, content);
        }
        ctx.it = content;
        return content;
      }

      const targets = await getTarget();
      for (const el of targets) {
        const html = String(content);
        if (modifier === 'into') el.innerHTML = html;
        else if (modifier === 'before') el.insertAdjacentHTML('beforebegin', html);
        else if (modifier === 'after') el.insertAdjacentHTML('afterend', html);
        else if (modifier === 'at start of') el.insertAdjacentHTML('afterbegin', html);
        else if (modifier === 'at end of') el.insertAdjacentHTML('beforeend', html);
      }
      ctx.it = content;
      return content;
    }`,
    append: `
    case 'append': {
      const content = await evaluate(cmd.args[0], ctx);
      const targets = await getTarget();
      for (const el of targets) el.insertAdjacentHTML('beforeend', String(content));
      ctx.it = content;
      return content;
    }`,
    morph: `
    case 'morph': {
      const targets = await getTarget();
      const content = await evaluate(cmd.args[0], ctx);
      const contentStr = String(content);
      const isOuter = cmd.modifier === 'over';

      for (const target of targets) {
        try {
          if (isOuter) {
            morphlexMorph(target, contentStr);
          } else {
            morphlexMorphInner(target, contentStr);
          }
        } catch (error) {
          // Fallback to innerHTML/outerHTML if morph fails
          console.warn('[LokaScript] Morph failed, falling back:', error);
          if (isOuter) {
            target.outerHTML = contentStr;
          } else {
            target.innerHTML = contentStr;
          }
        }
      }
      ctx.it = targets.length === 1 ? targets[0] : targets;
      return ctx.it;
    }`,
    take: `
    case 'take': {
      const className = getClassName(await evaluate(cmd.args[0], ctx));
      const from = cmd.target ? await getTarget() : [ctx.me.parentElement!];
      for (const container of from) {
        const siblings = container.querySelectorAll('.' + className);
        siblings.forEach(el => el.classList.remove(className));
      }
      ctx.me.classList.add(className);
      return ctx.me;
    }`,
    increment: `
    case 'increment': {
      const target = cmd.args[0];
      const amount = cmd.args[1] ? await evaluate(cmd.args[1], ctx) : 1;

      if (target.type === 'variable') {
        const varName = target.name.slice(1);
        const map = target.scope === 'local' ? ctx.locals : globalVars;
        const current = map.get(varName) || 0;
        const newVal = current + amount;
        map.set(varName, newVal);
        ctx.it = newVal;
        return newVal;
      }

      const elements = toElementArray(await evaluate(target, ctx));
      for (const el of elements) {
        const current = parseFloat(el.textContent || '0') || 0;
        const newVal = current + amount;
        el.textContent = String(newVal);
        ctx.it = newVal;
      }
      return ctx.it;
    }`,
    decrement: `
    case 'decrement': {
      const target = cmd.args[0];
      const amount = cmd.args[1] ? await evaluate(cmd.args[1], ctx) : 1;

      if (target.type === 'variable') {
        const varName = target.name.slice(1);
        const map = target.scope === 'local' ? ctx.locals : globalVars;
        const current = map.get(varName) || 0;
        const newVal = current - amount;
        map.set(varName, newVal);
        ctx.it = newVal;
        return newVal;
      }

      const elements = toElementArray(await evaluate(target, ctx));
      for (const el of elements) {
        const current = parseFloat(el.textContent || '0') || 0;
        const newVal = current - amount;
        el.textContent = String(newVal);
        ctx.it = newVal;
      }
      return ctx.it;
    }`,
    focus: `
    case 'focus': {
      const targets = await getTarget();
      for (const el of targets) (el as HTMLElement).focus();
      return targets;
    }`,
    blur: `
    case 'blur': {
      const targets = await getTarget();
      for (const el of targets) (el as HTMLElement).blur();
      return targets;
    }`,
    return: `
    case 'return': {
      const value = cmd.args[0] ? await evaluate(cmd.args[0], ctx) : ctx.it;
      throw { type: 'return', value };
    }`,
    break: `
    case 'break': {
      throw { type: 'break' };
    }`,
    continue: `
    case 'continue': {
      throw { type: 'continue' };
    }`,
    halt: `
    case 'halt': {
      // Check for "halt the event" pattern
      const firstArg = cmd.args[0];
      let targetEvent = ctx.event;
      if (firstArg?.type === 'identifier' && firstArg.name === 'the' && cmd.args[1]?.name === 'event') {
        targetEvent = ctx.event;
      } else if (firstArg) {
        const evaluated = await evaluate(firstArg, ctx);
        if (evaluated?.preventDefault) targetEvent = evaluated;
      }

      if (targetEvent && typeof targetEvent.preventDefault === 'function') {
        targetEvent.preventDefault();
        targetEvent.stopPropagation();
        return { halted: true, eventHalted: true };
      }

      // Regular halt - stop execution
      const haltError = new Error('HALT_EXECUTION');
      (haltError as any).isHalt = true;
      throw haltError;
    }`,
    exit: `
    case 'exit': {
      const exitError = new Error('EXIT_COMMAND');
      (exitError as any).isExit = true;
      throw exitError;
    }`,
    throw: `
    case 'throw': {
      const message = cmd.args[0] ? await evaluate(cmd.args[0], ctx) : 'Error';
      const errorToThrow = message instanceof Error ? message : new Error(String(message));
      throw errorToThrow;
    }`,
    beep: `
    case 'beep': {
      const values = await Promise.all(cmd.args.map((a: any) => evaluate(a, ctx)));
      const displayValues = values.length > 0 ? values : [ctx.it];

      for (const val of displayValues) {
        const typeInfo = val === null ? 'null' :
                        val === undefined ? 'undefined' :
                        Array.isArray(val) ? \`Array[\${val.length}]\` :
                        val instanceof Element ? \`Element<\${val.tagName.toLowerCase()}>\` :
                        typeof val;
        console.log('[beep]', typeInfo + ':', val);
      }
      return displayValues[0];
    }`,
    js: `
    case 'js': {
      const codeArg = cmd.args[0];
      let jsCode = '';

      if (codeArg.type === 'string') {
        jsCode = codeArg.value;
      } else if (codeArg.type === 'template') {
        jsCode = await evaluate(codeArg, ctx);
      } else {
        jsCode = String(await evaluate(codeArg, ctx));
      }

      // Build context object for the Function
      const jsContext = {
        me: ctx.me,
        it: ctx.it,
        event: ctx.event,
        target: ctx.target || ctx.me,
        locals: Object.fromEntries(ctx.locals),
        globals: Object.fromEntries(globalVars),
        document: typeof document !== 'undefined' ? document : undefined,
        window: typeof window !== 'undefined' ? window : undefined,
      };

      try {
        const fn = new Function('ctx', \`with(ctx) { return (async () => { \${jsCode} })(); }\`);
        const result = await fn(jsContext);
        ctx.it = result;
        return result;
      } catch (error) {
        console.error('[js] Execution error:', error);
        throw error;
      }
    }`,
    copy: `
    case 'copy': {
      const source = await evaluate(cmd.args[0], ctx);
      let textToCopy = '';

      if (typeof source === 'string') {
        textToCopy = source;
      } else if (source instanceof Element) {
        textToCopy = source.textContent || '';
      } else {
        textToCopy = String(source);
      }

      try {
        if (navigator.clipboard) {
          await navigator.clipboard.writeText(textToCopy);
        } else {
          // Fallback for older browsers
          const textarea = document.createElement('textarea');
          textarea.value = textToCopy;
          textarea.style.cssText = 'position:fixed;top:0;left:-9999px';
          document.body.appendChild(textarea);
          textarea.select();
          document.execCommand('copy');
          document.body.removeChild(textarea);
        }

        if (ctx.me instanceof Element) {
          ctx.me.dispatchEvent(new CustomEvent('copy:success', {
            bubbles: true,
            detail: { text: textToCopy }
          }));
        }
        ctx.it = textToCopy;
        return textToCopy;
      } catch (error) {
        if (ctx.me instanceof Element) {
          ctx.me.dispatchEvent(new CustomEvent('copy:error', {
            bubbles: true,
            detail: { error }
          }));
        }
        throw error;
      }
    }`,
    push: `
    case 'push':
    case 'push-url': {
      // Handle "push url '/path'" pattern
      let urlArg = cmd.args[0];
      if (urlArg?.type === 'identifier' && urlArg.name === 'url') {
        urlArg = cmd.args[1];
      }

      const url = String(await evaluate(urlArg, ctx));
      let title = '';

      // Check for "with title" modifier
      if (cmd.modifiers?.title) {
        title = String(await evaluate(cmd.modifiers.title, ctx));
      }

      window.history.pushState(null, '', url);
      if (title) document.title = title;

      window.dispatchEvent(new CustomEvent('lokascript:pushurl', {
        detail: { url, title }
      }));

      return { url, title, mode: 'push' };
    }`,
    replace: `
    case 'replace':
    case 'replace-url': {
      // Handle "replace url '/path'" pattern
      let urlArg = cmd.args[0];
      if (urlArg?.type === 'identifier' && urlArg.name === 'url') {
        urlArg = cmd.args[1];
      }

      const url = String(await evaluate(urlArg, ctx));
      let title = '';

      // Check for "with title" modifier
      if (cmd.modifiers?.title) {
        title = String(await evaluate(cmd.modifiers.title, ctx));
      }

      window.history.replaceState(null, '', url);
      if (title) document.title = title;

      window.dispatchEvent(new CustomEvent('lokascript:replaceurl', {
        detail: { url, title }
      }));

      return { url, title, mode: 'replace' };
    }`,
};
const BLOCK_IMPLEMENTATIONS_TS = {
    if: `
    case 'if': {
      const condition = await evaluate(block.condition!, ctx);
      if (condition) {
        return executeSequenceWithBlocks(block.body, ctx);
      } else if (block.elseBody) {
        return executeSequenceWithBlocks(block.elseBody, ctx);
      }
      return null;
    }`,
    repeat: `
    case 'repeat': {
      const count = await evaluate(block.condition!, ctx);
      const n = typeof count === 'number' ? count : parseInt(String(count));
      for (let i = 0; i < n && i < MAX_LOOP_ITERATIONS; i++) {
        ctx.locals.set('__loop_index__', i);
        ctx.locals.set('__loop_count__', i + 1);
        try {
          await executeSequenceWithBlocks(block.body, ctx);
        } catch (e: any) {
          if (e?.type === 'break') break;
          if (e?.type === 'continue') continue;
          throw e;
        }
      }
      return null;
    }`,
    for: `
    case 'for': {
      const { variable, iterable } = block.condition as any;
      const items = await evaluate(iterable, ctx);
      const arr = Array.isArray(items) ? items : items instanceof NodeList ? Array.from(items) : [items];
      const varName = variable.startsWith(':') ? variable.slice(1) : variable;
      for (let i = 0; i < arr.length && i < MAX_LOOP_ITERATIONS; i++) {
        ctx.locals.set(varName, arr[i]);
        ctx.locals.set('__loop_index__', i);
        ctx.locals.set('__loop_count__', i + 1);
        try {
          await executeSequenceWithBlocks(block.body, ctx);
        } catch (e: any) {
          if (e?.type === 'break') break;
          if (e?.type === 'continue') continue;
          throw e;
        }
      }
      return null;
    }`,
    while: `
    case 'while': {
      let iterations = 0;
      while (await evaluate(block.condition!, ctx) && iterations < MAX_LOOP_ITERATIONS) {
        ctx.locals.set('__loop_index__', iterations);
        try {
          await executeSequenceWithBlocks(block.body, ctx);
        } catch (e: any) {
          if (e?.type === 'break') break;
          if (e?.type === 'continue') { iterations++; continue; }
          throw e;
        }
        iterations++;
      }
      return null;
    }`,
    fetch: `
    case 'fetch': {
      const { url, responseType } = block.condition as any;
      try {
        const urlVal = await evaluate(url, ctx);
        const response = await fetch(String(urlVal));
        if (!response.ok) throw new Error(\`HTTP \${response.status}\`);

        const resType = await evaluate(responseType, ctx);
        let data: any;
        if (resType === 'json') data = await response.json();
        else if (resType === 'html') {
          const text = await response.text();
          const doc = new DOMParser().parseFromString(text, 'text/html');
          data = doc.body.innerHTML;
        } else data = await response.text();

        ctx.it = data;
        ctx.locals.set('it', data);
        ctx.locals.set('result', data);
        ctx.locals.set('response', response);

        await executeSequenceWithBlocks(block.body, ctx);
      } catch (error: any) {
        if (error?.type === 'return') throw error;
        ctx.locals.set('error', error);
        console.error('Fetch error:', error);
      }
      return null;
    }`,
};
const STYLE_COMMANDS = ['set', 'put', 'increment', 'decrement'];
const ELEMENT_ARRAY_COMMANDS = ['put', 'increment', 'decrement'];
const MORPH_COMMANDS = ['morph'];
function getCommandImplementations(format = 'ts') {
    const result = {};
    for (const [name, code] of Object.entries(COMMAND_IMPLEMENTATIONS_TS)) {
        result[name] = stripTypes(code, format);
    }
    return result;
}
function getBlockImplementations(format = 'ts') {
    const result = {};
    for (const [name, code] of Object.entries(BLOCK_IMPLEMENTATIONS_TS)) {
        result[name] = stripTypes(code, format);
    }
    return result;
}
function getAvailableCommands() {
    return Object.keys(COMMAND_IMPLEMENTATIONS_TS);
}
function getAvailableBlocks() {
    return Object.keys(BLOCK_IMPLEMENTATIONS_TS);
}
const COMMAND_IMPLEMENTATIONS = COMMAND_IMPLEMENTATIONS_TS;
const BLOCK_IMPLEMENTATIONS = BLOCK_IMPLEMENTATIONS_TS;

function generateBundleCode(config) {
    const { name, commands, blocks = [], htmxIntegration = false, globalName = 'hyperfixi', positionalExpressions = false, parserImportPath = '../parser/hybrid', autoInit = true, esModule = true, format = 'ts', maxLoopIterations = 1000, } = config;
    const needsStyleHelpers = commands.some(cmd => STYLE_COMMANDS.includes(cmd));
    const needsElementArrayHelper = commands.some(cmd => ELEMENT_ARRAY_COMMANDS.includes(cmd));
    const hasBlocks = blocks.length > 0;
    const hasReturn = commands.includes('return');
    const commandImpls = getCommandImplementations(format);
    const blockImpls = getBlockImplementations(format);
    const commandCases = commands
        .filter(cmd => commandImpls[cmd])
        .map(cmd => commandImpls[cmd])
        .join('\n');
    const blockCases = blocks
        .filter(block => blockImpls[block])
        .map(block => blockImpls[block])
        .join('\n');
    return `/**
 * HyperFixi ${name} Bundle (Auto-Generated)
 *
 * Generated by: @lokascript/core bundle-generator
 * Commands: ${commands.join(', ')}${blocks.length > 0 ? `\n * Blocks: ${blocks.join(', ')}` : ''}${positionalExpressions ? '\n * Positional expressions: enabled' : ''}
 *
 * DO NOT EDIT - Regenerate with the build script.
 */

// =============================================================================
// MODULAR PARSER IMPORTS
// =============================================================================

import { HybridParser } from '${parserImportPath}/parser-core';
import type { ASTNode, CommandNode, EventNode${hasBlocks ? ', BlockNode' : ''} } from '${parserImportPath}/ast-types';

// =============================================================================
// RUNTIME
// =============================================================================

interface Context {
  me: Element;
  event?: Event;
  it?: any;
  you?: Element;
  locals: Map<string, any>;
}

const globalVars = new Map<string, any>();
${hasBlocks ? `const MAX_LOOP_ITERATIONS = ${maxLoopIterations};` : ''}

async function evaluate(node: ASTNode, ctx: Context): Promise<any> {
  switch (node.type) {
    case 'literal': return node.value;

    case 'identifier':
      if (node.value === 'me' || node.value === 'my') return ctx.me;
      if (node.value === 'it') return ctx.it;
      if (node.value === 'you') return ctx.you;
      if (node.value === 'event') return ctx.event;
      if (node.value === 'body') return document.body;
      if (node.value === 'document') return document;
      if (node.value === 'window') return window;
      if (ctx.locals.has(node.value)) return ctx.locals.get(node.value);
      if (node.value in (ctx.me as any)) return (ctx.me as any)[node.value];
      return node.value;

    case 'variable':
      if (node.scope === 'local') return ctx.locals.get(node.name.slice(1));
      const gName = node.name.slice(1);
      if (globalVars.has(gName)) return globalVars.get(gName);
      return (window as any)[node.name];

    case 'selector':
      const elements = document.querySelectorAll(node.value);
      return elements.length === 1 ? elements[0] : Array.from(elements);

    case 'binary':
      return evaluateBinary(node, ctx);

    case 'possessive':
    case 'member':
      const obj = await evaluate(node.object, ctx);
      if (obj == null) return undefined;
      const prop = node.computed ? await evaluate(node.property, ctx) : node.property;
      return obj[prop];

    case 'call': {
      let callContext: any = null;
      let callee: any;

      if (node.callee.type === 'member' || node.callee.type === 'possessive') {
        callContext = await evaluate(node.callee.object, ctx);
        const p = node.callee.computed
          ? await evaluate(node.callee.property, ctx)
          : node.callee.property;
        callee = callContext?.[p];
      } else {
        callee = await evaluate(node.callee, ctx);
      }

      const args = await Promise.all(node.args.map((a: ASTNode) => evaluate(a, ctx)));
      if (typeof callee === 'function') return callee.apply(callContext, args);
      return undefined;
    }
${positionalExpressions
        ? `
    case 'positional':
      return evaluatePositional(node, ctx);
`
        : ''}
    default: return undefined;
  }
}

async function evaluateBinary(node: ASTNode, ctx: Context): Promise<any> {
  if (node.operator === 'has') {
    const left = await evaluate(node.left, ctx);
    if (left instanceof Element && node.right.type === 'selector' && node.right.value.startsWith('.')) {
      return left.classList.contains(node.right.value.slice(1));
    }
    return false;
  }

  const left = await evaluate(node.left, ctx);
  const right = await evaluate(node.right, ctx);

  switch (node.operator) {
    case '+': return left + right;
    case '-': return left - right;
    case '*': return left * right;
    case '/': return left / right;
    case '%': return left % right;
    case '==': case 'is': return left == right;
    case '!=': case 'is not': return left != right;
    case '<': return left < right;
    case '>': return left > right;
    case '<=': return left <= right;
    case '>=': return left >= right;
    case 'and': case '&&': return left && right;
    case 'or': case '||': return left || right;
    case 'contains': case 'includes':
      if (typeof left === 'string') return left.includes(right);
      if (Array.isArray(left)) return left.includes(right);
      if (left instanceof Element) return left.contains(right);
      return false;
    case 'matches':
      if (left instanceof Element) return left.matches(right);
      if (typeof left === 'string') return new RegExp(right).test(left);
      return false;
    default: return undefined;
  }
}
${positionalExpressions
        ? `
function evaluatePositional(node: ASTNode, ctx: Context): Element | null {
  const target = node.target;
  let elements: Element[] = [];

  let selector: string | null = null;
  if (target.type === 'selector') {
    selector = target.value;
  } else if (target.type === 'identifier') {
    selector = target.value;
  } else if (target.type === 'htmlSelector') {
    selector = target.value;
  }
  if (selector) {
    elements = Array.from(document.querySelectorAll(selector));
  }

  switch (node.position) {
    case 'first': return elements[0] || null;
    case 'last': return elements[elements.length - 1] || null;
    case 'next': return ctx.me.nextElementSibling;
    case 'previous': return ctx.me.previousElementSibling;
    case 'closest': return target.value ? ctx.me.closest(target.value) : null;
    case 'parent': return ctx.me.parentElement;
    default: return elements[0] || null;
  }
}
`
        : ''}
// =============================================================================
// COMMAND EXECUTOR
// =============================================================================

${needsStyleHelpers
        ? `
const isStyleProp = (prop: string) => prop?.startsWith('*');
const getStyleName = (prop: string) => prop.substring(1);
const setStyleProp = (el: Element, prop: string, value: any): boolean => {
  if (!isStyleProp(prop)) return false;
  (el as HTMLElement).style.setProperty(getStyleName(prop), String(value));
  return true;
};
`
        : ''}

${needsElementArrayHelper
        ? `
const toElementArray = (val: any): Element[] => {
  if (Array.isArray(val)) return val.filter(e => e instanceof Element);
  if (val instanceof Element) return [val];
  if (typeof val === 'string') return Array.from(document.querySelectorAll(val));
  return [];
};
`
        : ''}

async function executeCommand(cmd: CommandNode, ctx: Context): Promise<any> {
  const getTarget = async (): Promise<Element[]> => {
    if (!cmd.target) return [ctx.me];
    const t = await evaluate(cmd.target, ctx);
    if (Array.isArray(t)) return t;
    if (t instanceof Element) return [t];
    if (typeof t === 'string') return Array.from(document.querySelectorAll(t));
    return [ctx.me];
  };

  const getClassName = (node: any): string => {
    if (!node) return '';
    if (node.type === 'selector') return node.value.slice(1);
    if (node.type === 'string' || node.type === 'literal') {
      const val = node.value;
      return typeof val === 'string' && val.startsWith('.') ? val.slice(1) : String(val);
    }
    if (node.type === 'identifier') return node.value;
    return '';
  };

  switch (cmd.name) {
${commandCases}

    default:
      console.warn(\`${name} bundle: Unknown command '\${cmd.name}'\`);
      return null;
  }
}
${hasBlocks
        ? `
// =============================================================================
// BLOCK EXECUTOR
// =============================================================================

async function executeBlock(block: BlockNode, ctx: Context): Promise<any> {
  switch (block.type) {
${blockCases}

    default:
      console.warn(\`${name} bundle: Unknown block '\${block.type}'\`);
      return null;
  }
}
`
        : ''}
// =============================================================================
// SEQUENCE EXECUTOR
// =============================================================================

async function executeSequence(nodes: ASTNode[], ctx: Context): Promise<any> {
  let result: any;
  for (const node of nodes) {
    if (node.type === 'command') {
      result = await executeCommand(node as CommandNode, ctx);
    }${hasBlocks
        ? ` else if (['if', 'repeat', 'for', 'while', 'fetch'].includes(node.type)) {
      result = await executeBlock(node as BlockNode, ctx);
    }`
        : ''}
  }
  return result;
}
${hasBlocks
        ? `
async function executeSequenceWithBlocks(nodes: ASTNode[], ctx: Context): Promise<any> {
  try {
    return await executeSequence(nodes, ctx);
  } catch (e: any) {
    if (e?.type === 'return') throw e;
    throw e;
  }
}
`
        : ''}
async function executeAST(ast: ASTNode, me: Element, event?: Event): Promise<any> {
  const ctx: Context = { me, event, locals: new Map() };

  if (ast.type === 'sequence') {
    ${hasReturn || hasBlocks ? "try { return await executeSequence(ast.commands, ctx); } catch (e: any) { if (e?.type === 'return') return e.value; throw e; }" : 'return executeSequence(ast.commands, ctx);'}
  }

  if (ast.type === 'event') {
    const eventNode = ast as EventNode;
    const eventName = eventNode.event;

    if (eventName === 'init') {
      ${hasReturn || hasBlocks ? "try { await executeSequence(eventNode.body, ctx); } catch (e: any) { if (e?.type !== 'return') throw e; }" : 'await executeSequence(eventNode.body, ctx);'}
      return;
    }

    const target = eventNode.filter ? await evaluate(eventNode.filter, ctx) : me;
    const targetEl = target instanceof Element ? target : me;
    const mods = eventNode.modifiers;

    let handler = async (e: Event) => {
      if (mods.prevent) e.preventDefault();
      if (mods.stop) e.stopPropagation();

      const handlerCtx: Context = {
        me, event: e,
        you: e.target instanceof Element ? e.target : undefined,
        locals: new Map(),
      };
      ${hasReturn || hasBlocks ? "try { await executeSequence(eventNode.body, handlerCtx); } catch (err: any) { if (err?.type !== 'return') throw err; }" : 'await executeSequence(eventNode.body, handlerCtx);'}
    };

    if (mods.debounce) {
      let timeout: any;
      const orig = handler;
      handler = async (e: Event) => {
        clearTimeout(timeout);
        timeout = setTimeout(() => orig(e), mods.debounce);
      };
    }

    if (mods.throttle) {
      let lastCall = 0;
      const orig = handler;
      handler = async (e: Event) => {
        const now = Date.now();
        if (now - lastCall >= mods.throttle!) {
          lastCall = now;
          await orig(e);
        }
      };
    }

    targetEl.addEventListener(eventName, handler, { once: !!mods.once });
    return;
  }

  return null;
}

// =============================================================================
// DOM PROCESSOR
// =============================================================================

function processElement(el: Element): void {
  const code = el.getAttribute('_');
  if (!code) return;

  try {
    const parser = new HybridParser(code);
    const ast = parser.parse();
    executeAST(ast, el);
  } catch (err) {
    console.error('HyperFixi ${name} error:', err, 'Code:', code);
  }
}

function processElements(root: Element | Document = document): void {
  const elements = root.querySelectorAll('[_]');
  elements.forEach(processElement);
}

// =============================================================================
// PUBLIC API
// =============================================================================

const api = {
  version: '1.0.0-${name.toLowerCase().replace(/\s+/g, '-')}',

  parse(code: string): ASTNode {
    const parser = new HybridParser(code);
    return parser.parse();
  },

  async execute(code: string, element?: Element): Promise<any> {
    const me = element || document.body;
    const parser = new HybridParser(code);
    const ast = parser.parse();
    return executeAST(ast, me);
  },

  run: async (code: string, element?: Element) => api.execute(code, element),
  eval: async (code: string, element?: Element) => api.execute(code, element),

  init: processElements,
  process: processElements,

  commands: ${JSON.stringify(commands)},
  ${blocks.length > 0 ? `blocks: ${JSON.stringify(blocks)},` : ''}
  parserName: 'hybrid',
};

// =============================================================================
// AUTO-INITIALIZE
// =============================================================================
${autoInit
        ? `
if (typeof window !== 'undefined') {
  (window as any).${globalName} = api;
  (window as any)._hyperscript = api;

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => processElements());
  } else {
    processElements();
  }
${htmxIntegration
            ? `
  // HTMX integration
  document.addEventListener('htmx:afterSettle', (e: Event) => {
    const target = (e as CustomEvent).detail?.target;
    if (target) processElements(target);
  });
`
            : ''}
}
`
        : ''}
${esModule
        ? `
export default api;
export { api, processElements };
`
        : ''}`;
}
function generateBundle(config) {
    const warnings = [];
    const errors = [];
    const strict = config.validation?.strict ?? false;
    const unknownCommands = config.commands.filter(cmd => !COMMAND_IMPLEMENTATIONS[cmd]);
    for (const cmd of unknownCommands) {
        const error = {
            type: 'unknown-command',
            message: `Unknown command '${cmd}' will not be included`,
            name: cmd,
        };
        errors.push(error);
        warnings.push(error.message);
    }
    const unknownBlocks = (config.blocks || []).filter(block => !BLOCK_IMPLEMENTATIONS[block]);
    for (const block of unknownBlocks) {
        const error = {
            type: 'unknown-block',
            message: `Unknown block '${block}' will not be included`,
            name: block,
        };
        errors.push(error);
        warnings.push(error.message);
    }
    if (strict && errors.length > 0) {
        const errorMessages = errors.map(e => e.message).join('; ');
        throw new Error(`Bundle generation failed (strict mode): ${errorMessages}`);
    }
    const code = generateBundleCode(config);
    return {
        code,
        commands: config.commands.filter(cmd => COMMAND_IMPLEMENTATIONS[cmd]),
        blocks: (config.blocks || []).filter(block => BLOCK_IMPLEMENTATIONS[block]),
        positional: config.positionalExpressions || false,
        warnings,
        errors,
    };
}

const LITE_PARSER_TEMPLATE = `
// Lite Parser - Regex-based for minimal bundle size

function parseLite(code) {
  const trimmed = code.trim();

  // Handle event handlers: "on click toggle .active"
  const onMatch = trimmed.match(/^on\\s+(\\w+)(?:\\s+from\\s+([^\\s]+))?\\s+(.+)$/i);
  if (onMatch) {
    return {
      type: 'event',
      event: onMatch[1],
      filter: onMatch[2] ? { type: 'selector', value: onMatch[2] } : undefined,
      modifiers: {},
      body: parseCommands(onMatch[3]),
    };
  }

  // Handle "every Nms" event pattern
  const everyMatch = trimmed.match(/^every\\s+(\\d+)(ms|s)?\\s+(.+)$/i);
  if (everyMatch) {
    const ms = everyMatch[2] === 's' ? parseInt(everyMatch[1]) * 1000 : parseInt(everyMatch[1]);
    return {
      type: 'event',
      event: 'interval:' + ms,
      modifiers: {},
      body: parseCommands(everyMatch[3]),
    };
  }

  // Handle "init" pattern
  const initMatch = trimmed.match(/^init\\s+(.+)$/i);
  if (initMatch) {
    return {
      type: 'event',
      event: 'init',
      modifiers: {},
      body: parseCommands(initMatch[1]),
    };
  }

  return { type: 'sequence', commands: parseCommands(trimmed) };
}

function parseCommands(code) {
  const parts = code.split(/\\s+(?:then|and)\\s+/i);
  return parts.map(parseCommand).filter(Boolean);
}

function parseCommand(code) {
  const trimmed = code.trim();
  if (!trimmed) return null;

  let match;

  // toggle .class [on target]
  match = trimmed.match(/^toggle\\s+(\\.\\w+|\\w+)(?:\\s+on\\s+(.+))?$/i);
  if (match) {
    return {
      type: 'command',
      name: 'toggle',
      args: [{ type: 'selector', value: match[1] }],
      target: match[2] ? parseTarget(match[2]) : undefined,
    };
  }

  // add .class [to target]
  match = trimmed.match(/^add\\s+(\\.\\w+|\\w+)(?:\\s+to\\s+(.+))?$/i);
  if (match) {
    return {
      type: 'command',
      name: 'add',
      args: [{ type: 'selector', value: match[1] }],
      target: match[2] ? parseTarget(match[2]) : undefined,
    };
  }

  // remove .class [from target] | remove [target]
  match = trimmed.match(/^remove\\s+(\\.\\w+)(?:\\s+from\\s+(.+))?$/i);
  if (match) {
    return {
      type: 'command',
      name: 'removeClass',
      args: [{ type: 'selector', value: match[1] }],
      target: match[2] ? parseTarget(match[2]) : undefined,
    };
  }
  match = trimmed.match(/^remove\\s+(.+)$/i);
  if (match) {
    return {
      type: 'command',
      name: 'remove',
      args: [],
      target: parseTarget(match[1]),
    };
  }

  // put "content" into target
  match = trimmed.match(/^put\\s+(?:"([^"]+)"|'([^']+)'|(\\S+))\\s+(into|before|after)\\s+(.+)$/i);
  if (match) {
    const content = match[1] || match[2] || match[3];
    return {
      type: 'command',
      name: 'put',
      args: [{ type: 'literal', value: content }],
      modifier: match[4],
      target: parseTarget(match[5]),
    };
  }

  // set target to value | set :var to value
  match = trimmed.match(/^set\\s+(.+?)\\s+to\\s+(.+)$/i);
  if (match) {
    return {
      type: 'command',
      name: 'set',
      args: [parseTarget(match[1]), parseLiteValue(match[2])],
    };
  }

  // log message
  match = trimmed.match(/^log\\s+(.+)$/i);
  if (match) {
    return {
      type: 'command',
      name: 'log',
      args: [parseLiteValue(match[1])],
    };
  }

  // send event [to target]
  match = trimmed.match(/^send\\s+(\\w+)(?:\\s+to\\s+(.+))?$/i);
  if (match) {
    return {
      type: 'command',
      name: 'send',
      args: [{ type: 'literal', value: match[1] }],
      target: match[2] ? parseTarget(match[2]) : undefined,
    };
  }

  // wait Nms | wait Ns
  match = trimmed.match(/^wait\\s+(\\d+)(ms|s)?$/i);
  if (match) {
    const ms = match[2] === 's' ? parseInt(match[1]) * 1000 : parseInt(match[1]);
    return {
      type: 'command',
      name: 'wait',
      args: [{ type: 'literal', value: ms }],
    };
  }

  // show/hide shortcuts
  match = trimmed.match(/^(show|hide)(?:\\s+(.+))?$/i);
  if (match) {
    return {
      type: 'command',
      name: match[1].toLowerCase(),
      args: [],
      target: match[2] ? parseTarget(match[2]) : undefined,
    };
  }

  // Unknown command - try generic parsing
  const parts = trimmed.split(/\\s+/);
  if (parts.length > 0) {
    return {
      type: 'command',
      name: parts[0],
      args: parts.slice(1).map(p => ({ type: 'literal', value: p })),
    };
  }

  return null;
}

function parseTarget(str) {
  const s = str.trim();
  if (s === 'me') return { type: 'identifier', value: 'me' };
  if (s === 'body') return { type: 'identifier', value: 'body' };
  if (s.startsWith('#') || s.startsWith('.') || s.startsWith('[')) {
    return { type: 'selector', value: s };
  }
  if (s.startsWith(':')) {
    return { type: 'variable', name: s, scope: 'local' };
  }
  return { type: 'identifier', value: s };
}

function parseLiteValue(str) {
  const s = str.trim();
  if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
    return { type: 'literal', value: s.slice(1, -1) };
  }
  if (/^-?\\d+(\\.\\d+)?$/.test(s)) {
    return { type: 'literal', value: parseFloat(s) };
  }
  if (s === 'true') return { type: 'literal', value: true };
  if (s === 'false') return { type: 'literal', value: false };
  if (s === 'null') return { type: 'literal', value: null };
  if (s.startsWith(':')) return { type: 'variable', name: s, scope: 'local' };
  if (s === 'me') return { type: 'identifier', value: 'me' };
  return { type: 'identifier', value: s };
}
`;
const HYBRID_PARSER_TEMPLATE = `
// Hybrid Parser - Full AST with operator precedence

// Tokenizer
const KEYWORDS = new Set([
  'on', 'from', 'to', 'into', 'before', 'after', 'in', 'of', 'at', 'with',
  'if', 'else', 'unless', 'end', 'then', 'and', 'or', 'not',
  'repeat', 'times', 'for', 'each', 'while', 'until',
  'toggle', 'add', 'remove', 'put', 'set', 'get', 'call', 'return', 'append',
  'log', 'send', 'trigger', 'wait', 'settle', 'fetch', 'as',
  'show', 'hide', 'take', 'increment', 'decrement', 'focus', 'blur', 'go', 'transition', 'over',
  'the', 'a', 'an', 'my', 'its', 'me', 'it', 'you',
  'first', 'last', 'next', 'previous', 'closest', 'parent',
  'true', 'false', 'null', 'undefined',
  'is', 'matches', 'contains', 'includes', 'exists', 'has', 'init', 'every', 'by',
]);

const COMMAND_ALIASES = {
  flip: 'toggle', switch: 'toggle', display: 'show', reveal: 'show',
  conceal: 'hide', increase: 'increment', decrease: 'decrement',
  fire: 'trigger', dispatch: 'send', navigate: 'go', goto: 'go',
};

const EVENT_ALIASES = {
  clicked: 'click', pressed: 'keydown', changed: 'change',
  submitted: 'submit', loaded: 'load',
};

function normalizeCommand(name) {
  const lower = name.toLowerCase();
  return COMMAND_ALIASES[lower] || lower;
}

function normalizeEvent(name) {
  const lower = name.toLowerCase();
  return EVENT_ALIASES[lower] || lower;
}

function tokenize(code) {
  const tokens = [];
  let pos = 0;

  while (pos < code.length) {
    if (/\\s/.test(code[pos])) { pos++; continue; }
    if (code.slice(pos, pos + 2) === '--') {
      while (pos < code.length && code[pos] !== '\\n') pos++;
      continue;
    }

    const start = pos;

    // HTML selector <tag/>
    if (code[pos] === '<' && /[a-zA-Z]/.test(code[pos + 1] || '')) {
      pos++;
      while (pos < code.length && code[pos] !== '>') pos++;
      if (code[pos] === '>') pos++;
      const val = code.slice(start, pos);
      if (val.endsWith('/>') || val.endsWith('>')) {
        const normalized = val.slice(1).replace(/\\/?>$/, '');
        tokens.push({ type: 'selector', value: normalized, pos: start });
        continue;
      }
    }

    // Possessive 's
    if (code.slice(pos, pos + 2) === "'s" && !/[a-zA-Z]/.test(code[pos + 2] || '')) {
      tokens.push({ type: 'operator', value: "'s", pos: start });
      pos += 2;
      continue;
    }

    // String literals
    if (code[pos] === '"' || code[pos] === "'") {
      const quote = code[pos++];
      while (pos < code.length && code[pos] !== quote) {
        if (code[pos] === '\\\\') pos++;
        pos++;
      }
      pos++;
      tokens.push({ type: 'string', value: code.slice(start, pos), pos: start });
      continue;
    }

    // Numbers with units
    if (/\\d/.test(code[pos]) || (code[pos] === '-' && /\\d/.test(code[pos + 1] || ''))) {
      if (code[pos] === '-') pos++;
      while (pos < code.length && /[\\d.]/.test(code[pos])) pos++;
      if (code.slice(pos, pos + 2) === 'ms') pos += 2;
      else if (code[pos] === 's' && !/[a-zA-Z]/.test(code[pos + 1] || '')) pos++;
      else if (code.slice(pos, pos + 2) === 'px') pos += 2;
      tokens.push({ type: 'number', value: code.slice(start, pos), pos: start });
      continue;
    }

    // Local variable :name
    if (code[pos] === ':') {
      pos++;
      while (pos < code.length && /[\\w]/.test(code[pos])) pos++;
      tokens.push({ type: 'localVar', value: code.slice(start, pos), pos: start });
      continue;
    }

    // Global variable $name
    if (code[pos] === '$') {
      pos++;
      while (pos < code.length && /[\\w]/.test(code[pos])) pos++;
      tokens.push({ type: 'globalVar', value: code.slice(start, pos), pos: start });
      continue;
    }

    // CSS selectors: #id, .class
    if (code[pos] === '#' || code[pos] === '.') {
      if (code[pos] === '.') {
        const afterDot = code.slice(pos + 1).match(/^(once|prevent|stop|debounce|throttle)\\b/i);
        if (afterDot) {
          tokens.push({ type: 'symbol', value: '.', pos: start });
          pos++;
          continue;
        }
      }
      pos++;
      while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
      tokens.push({ type: 'selector', value: code.slice(start, pos), pos: start });
      continue;
    }

    // Array literal vs Attribute selector
    if (code[pos] === '[') {
      let lookahead = pos + 1;
      while (lookahead < code.length && /\\s/.test(code[lookahead])) lookahead++;
      const nextChar = code[lookahead] || '';
      const isArrayLiteral = /['"\\d\\[\\]:\\$\\-]/.test(nextChar) || nextChar === '';
      if (isArrayLiteral) {
        tokens.push({ type: 'symbol', value: '[', pos: start });
        pos++;
        continue;
      } else {
        pos++;
        let depth = 1;
        while (pos < code.length && depth > 0) {
          if (code[pos] === '[') depth++;
          if (code[pos] === ']') depth--;
          pos++;
        }
        tokens.push({ type: 'selector', value: code.slice(start, pos), pos: start });
        continue;
      }
    }

    if (code[pos] === ']') {
      tokens.push({ type: 'symbol', value: ']', pos: start });
      pos++;
      continue;
    }

    // Multi-char operators
    const twoChar = code.slice(pos, pos + 2);
    if (['==', '!=', '<=', '>=', '&&', '||'].includes(twoChar)) {
      tokens.push({ type: 'operator', value: twoChar, pos: start });
      pos += 2;
      continue;
    }

    // Style property *opacity
    if (code[pos] === '*' && /[a-zA-Z]/.test(code[pos + 1] || '')) {
      pos++;
      while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
      tokens.push({ type: 'styleProperty', value: code.slice(start, pos), pos: start });
      continue;
    }

    if ('+-*/%<>!'.includes(code[pos])) {
      tokens.push({ type: 'operator', value: code[pos], pos: start });
      pos++;
      continue;
    }

    if ('()[]{},.'.includes(code[pos])) {
      tokens.push({ type: 'symbol', value: code[pos], pos: start });
      pos++;
      continue;
    }

    if (/[a-zA-Z_]/.test(code[pos])) {
      while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
      const value = code.slice(start, pos);
      const type = KEYWORDS.has(value.toLowerCase()) ? 'keyword' : 'identifier';
      tokens.push({ type, value, pos: start });
      continue;
    }

    pos++;
  }

  tokens.push({ type: 'eof', value: '', pos: code.length });
  return tokens;
}

// Parser
class HybridParser {
  constructor(code) {
    this.tokens = tokenize(code);
    this.pos = 0;
  }

  peek(offset = 0) {
    return this.tokens[Math.min(this.pos + offset, this.tokens.length - 1)];
  }

  advance() {
    return this.tokens[this.pos++];
  }

  match(...values) {
    const token = this.peek();
    return values.some(v => token.value.toLowerCase() === v.toLowerCase());
  }

  matchType(...types) {
    return types.includes(this.peek().type);
  }

  expect(value) {
    if (!this.match(value) && normalizeCommand(this.peek().value) !== value) {
      throw new Error("Expected '" + value + "', got '" + this.peek().value + "'");
    }
    return this.advance();
  }

  isAtEnd() {
    return this.peek().type === 'eof';
  }

  parse() {
    if (this.match('on')) return this.parseEventHandler();
    if (this.match('init')) {
      this.advance();
      return { type: 'event', event: 'init', modifiers: {}, body: this.parseCommandSequence() };
    }
    if (this.match('every')) return this.parseEveryHandler();
    return { type: 'sequence', commands: this.parseCommandSequence() };
  }

  parseEventHandler() {
    this.expect('on');
    const eventName = this.advance().value;
    const modifiers = {};
    let filter;

    while (this.peek().value === '.') {
      this.advance();
      const mod = this.advance().value.toLowerCase();
      if (mod === 'once') modifiers.once = true;
      else if (mod === 'prevent') modifiers.prevent = true;
      else if (mod === 'stop') modifiers.stop = true;
      else if (mod === 'debounce' || mod === 'throttle') {
        if (this.peek().value === '(') {
          this.advance();
          const num = this.advance().value;
          this.expect(')');
          if (mod === 'debounce') modifiers.debounce = parseInt(num) || 100;
          else modifiers.throttle = parseInt(num) || 100;
        }
      }
    }

    if (this.match('from')) {
      this.advance();
      filter = this.parseExpression();
    }

    return { type: 'event', event: normalizeEvent(eventName), filter, modifiers, body: this.parseCommandSequence() };
  }

  parseEveryHandler() {
    this.expect('every');
    const interval = this.advance().value;
    return { type: 'event', event: 'interval:' + interval, modifiers: {}, body: this.parseCommandSequence() };
  }

  parseCommandSequence() {
    const commands = [];
    while (!this.isAtEnd() && !this.match('end', 'else')) {
      const cmd = this.parseCommand();
      if (cmd) commands.push(cmd);
      if (this.match('then', 'and')) this.advance();
    }
    return commands;
  }

  parseCommand() {
    if (this.match('if', 'unless')) return this.parseIf();
    if (this.match('repeat')) return this.parseRepeat();
    if (this.match('for')) return this.parseFor();
    if (this.match('while')) return this.parseWhile();
    if (this.match('fetch')) return this.parseFetchBlock();

    const cmdMap = {
      toggle: () => this.parseToggle(),
      add: () => this.parseAdd(),
      remove: () => this.parseRemove(),
      put: () => this.parsePut(),
      append: () => this.parseAppend(),
      set: () => this.parseSet(),
      get: () => this.parseGet(),
      call: () => this.parseCall(),
      log: () => this.parseLog(),
      send: () => this.parseSend(),
      trigger: () => this.parseSend(),
      wait: () => this.parseWait(),
      show: () => this.parseShow(),
      hide: () => this.parseHide(),
      take: () => this.parseTake(),
      increment: () => this.parseIncDec('increment'),
      decrement: () => this.parseIncDec('decrement'),
      focus: () => this.parseFocusBlur('focus'),
      blur: () => this.parseFocusBlur('blur'),
      go: () => this.parseGo(),
      return: () => this.parseReturn(),
      transition: () => this.parseTransition(),
    };

    const normalized = normalizeCommand(this.peek().value);
    if (cmdMap[normalized]) return cmdMap[normalized]();

    if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else')) this.advance();
    return null;
  }

  parseIf() {
    const isUnless = this.match('unless');
    this.advance();
    const condition = this.parseExpression();
    const body = this.parseCommandSequence();
    let elseBody;

    if (this.match('else')) {
      this.advance();
      elseBody = this.parseCommandSequence();
    }
    if (this.match('end')) this.advance();

    return {
      type: 'if',
      condition: isUnless ? { type: 'unary', operator: 'not', operand: condition } : condition,
      body,
      elseBody,
    };
  }

  parseRepeat() {
    this.expect('repeat');
    let count;
    if (!this.match('until', 'while', 'forever')) {
      count = this.parseExpression();
      if (this.match('times')) this.advance();
    }
    const body = this.parseCommandSequence();
    if (this.match('end')) this.advance();
    return { type: 'repeat', condition: count, body };
  }

  parseFor() {
    this.expect('for');
    if (this.match('each')) this.advance();
    const variable = this.advance().value;
    this.expect('in');
    const iterable = this.parseExpression();
    const body = this.parseCommandSequence();
    if (this.match('end')) this.advance();
    return { type: 'for', condition: { type: 'forCondition', variable, iterable }, body };
  }

  parseWhile() {
    this.expect('while');
    const condition = this.parseExpression();
    const body = this.parseCommandSequence();
    if (this.match('end')) this.advance();
    return { type: 'while', condition, body };
  }

  parseFetchBlock() {
    this.expect('fetch');
    const url = this.parseExpression();
    let responseType = { type: 'literal', value: 'text' };
    if (this.match('as')) {
      this.advance();
      responseType = this.parseExpression();
    }
    if (this.match('then')) this.advance();
    const body = this.parseCommandSequence();
    return { type: 'fetch', condition: { type: 'fetchConfig', url, responseType }, body };
  }

  parseToggle() {
    this.expect('toggle');
    const what = this.parseExpression();
    let target;
    if (this.match('on')) {
      this.advance();
      target = this.parseExpression();
    }
    return { type: 'command', name: 'toggle', args: [what], target };
  }

  parseAdd() {
    this.expect('add');
    const what = this.parseExpression();
    let target;
    if (this.match('to')) {
      this.advance();
      target = this.parseExpression();
    }
    return { type: 'command', name: 'add', args: [what], target };
  }

  parseRemove() {
    this.expect('remove');
    if (this.matchType('selector')) {
      const what = this.parseExpression();
      let target;
      if (this.match('from')) {
        this.advance();
        target = this.parseExpression();
      }
      return { type: 'command', name: 'removeClass', args: [what], target };
    }
    const target = this.parseExpression();
    return { type: 'command', name: 'remove', args: [], target };
  }

  parsePut() {
    this.expect('put');
    const content = this.parseExpression();
    let modifier = 'into';
    if (this.match('into', 'before', 'after', 'at')) {
      modifier = this.advance().value;
      if (modifier === 'at') {
        const pos = this.advance().value;
        this.expect('of');
        modifier = 'at ' + pos + ' of';
      }
    }
    const target = this.parseExpression();
    return { type: 'command', name: 'put', args: [content], target, modifier };
  }

  parseAppend() {
    this.expect('append');
    const content = this.parseExpression();
    let target;
    if (this.match('to')) {
      this.advance();
      target = this.parseExpression();
    }
    return { type: 'command', name: 'append', args: [content], target };
  }

  parseSet() {
    this.expect('set');
    const target = this.parseExpression();
    if (this.match('to')) {
      this.advance();
      const value = this.parseExpression();
      return { type: 'command', name: 'set', args: [target, value] };
    }
    return { type: 'command', name: 'set', args: [target] };
  }

  parseGet() {
    this.expect('get');
    return { type: 'command', name: 'get', args: [this.parseExpression()] };
  }

  parseCall() {
    this.expect('call');
    return { type: 'command', name: 'call', args: [this.parseExpression()] };
  }

  parseLog() {
    this.expect('log');
    const args = [];
    while (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else')) {
      args.push(this.parseExpression());
      if (this.match(',')) this.advance();
      else break;
    }
    return { type: 'command', name: 'log', args };
  }

  parseSend() {
    this.advance();
    const event = this.advance().value;
    let target;
    if (this.match('to')) {
      this.advance();
      target = this.parseExpression();
    }
    return { type: 'command', name: 'send', args: [{ type: 'literal', value: event }], target };
  }

  parseWait() {
    this.expect('wait');
    if (this.match('for')) {
      this.advance();
      const event = this.advance().value;
      let target;
      if (this.match('from')) {
        this.advance();
        target = this.parseExpression();
      }
      return { type: 'command', name: 'waitFor', args: [{ type: 'literal', value: event }], target };
    }
    return { type: 'command', name: 'wait', args: [this.parseExpression()] };
  }

  parseShow() {
    this.expect('show');
    let target;
    const modifiers = {};
    if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else', 'when', 'where')) {
      target = this.parseExpression();
    }
    if (!this.isAtEnd() && this.match('when', 'where')) {
      const keyword = this.advance().value;
      modifiers[keyword] = this.parseExpression();
    }
    return { type: 'command', name: 'show', args: [], target, modifiers };
  }

  parseHide() {
    this.expect('hide');
    let target;
    const modifiers = {};
    if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else', 'when', 'where')) {
      target = this.parseExpression();
    }
    if (!this.isAtEnd() && this.match('when', 'where')) {
      const keyword = this.advance().value;
      modifiers[keyword] = this.parseExpression();
    }
    return { type: 'command', name: 'hide', args: [], target, modifiers };
  }

  parseTake() {
    this.expect('take');
    const what = this.parseExpression();
    let from;
    if (this.match('from')) {
      this.advance();
      from = this.parseExpression();
    }
    return { type: 'command', name: 'take', args: [what], target: from };
  }

  parseIncDec(name) {
    this.advance();
    const target = this.parseExpression();
    let amount = { type: 'literal', value: 1 };
    if (this.match('by')) {
      this.advance();
      amount = this.parseExpression();
    }
    return { type: 'command', name, args: [target, amount] };
  }

  parseFocusBlur(name) {
    this.advance();
    let target;
    if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else')) {
      target = this.parseExpression();
    }
    return { type: 'command', name, args: [], target };
  }

  parseGo() {
    this.expect('go');
    if (this.match('to')) this.advance();
    if (this.match('url')) this.advance();
    const dest = this.parseExpression();
    return { type: 'command', name: 'go', args: [dest] };
  }

  parseReturn() {
    this.expect('return');
    let value;
    if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else')) {
      value = this.parseExpression();
    }
    return { type: 'command', name: 'return', args: value ? [value] : [] };
  }

  parseTransition() {
    this.expect('transition');
    let target;
    if (this.match('my', 'its')) {
      const ref = this.advance().value;
      target = { type: 'identifier', value: ref === 'my' ? 'me' : 'it' };
    } else if (this.matchType('selector')) {
      const expr = this.parseExpression();
      if (expr.type === 'possessive') {
        return this.parseTransitionRest(expr.object, expr.property);
      }
      target = expr;
    }

    const propToken = this.peek();
    let property;
    if (propToken.type === 'styleProperty') {
      property = this.advance().value;
    } else if (propToken.type === 'identifier' || propToken.type === 'keyword') {
      property = this.advance().value;
    } else {
      property = 'opacity';
    }

    return this.parseTransitionRest(target, property);
  }

  parseTransitionRest(target, property) {
    let toValue = { type: 'literal', value: 1 };
    if (this.match('to')) {
      this.advance();
      toValue = this.parseExpression();
    }
    let duration = { type: 'literal', value: 300 };
    if (this.match('over')) {
      this.advance();
      duration = this.parseExpression();
    }
    return { type: 'command', name: 'transition', args: [{ type: 'literal', value: property }, toValue, duration], target };
  }

  parseExpression() { return this.parseOr(); }

  parseOr() {
    let left = this.parseAnd();
    while (this.match('or', '||')) {
      this.advance();
      left = { type: 'binary', operator: 'or', left, right: this.parseAnd() };
    }
    return left;
  }

  parseAnd() {
    let left = this.parseEquality();
    while (this.match('and', '&&') && !this.isCommandKeyword(this.peek(1))) {
      this.advance();
      left = { type: 'binary', operator: 'and', left, right: this.parseEquality() };
    }
    return left;
  }

  isCommandKeyword(token) {
    const cmds = ['toggle', 'add', 'remove', 'set', 'put', 'log', 'send', 'wait', 'show', 'hide', 'increment', 'decrement', 'focus', 'blur', 'go'];
    return cmds.includes(normalizeCommand(token.value));
  }

  parseEquality() {
    let left = this.parseComparison();
    while (this.match('==', '!=', 'is', 'matches', 'contains', 'includes', 'has')) {
      const op = this.advance().value;
      if (op.toLowerCase() === 'is' && this.match('not')) {
        this.advance();
        left = { type: 'binary', operator: 'is not', left, right: this.parseComparison() };
      } else {
        left = { type: 'binary', operator: op, left, right: this.parseComparison() };
      }
    }
    return left;
  }

  parseComparison() {
    let left = this.parseAdditive();
    while (this.match('<', '>', '<=', '>=')) {
      const op = this.advance().value;
      left = { type: 'binary', operator: op, left, right: this.parseAdditive() };
    }
    return left;
  }

  parseAdditive() {
    let left = this.parseMultiplicative();
    while (this.match('+', '-')) {
      const op = this.advance().value;
      left = { type: 'binary', operator: op, left, right: this.parseMultiplicative() };
    }
    return left;
  }

  parseMultiplicative() {
    let left = this.parseUnary();
    while (this.match('*', '/', '%')) {
      const op = this.advance().value;
      left = { type: 'binary', operator: op, left, right: this.parseUnary() };
    }
    return left;
  }

  parseUnary() {
    if (this.match('not', '!')) {
      this.advance();
      return { type: 'unary', operator: 'not', operand: this.parseUnary() };
    }
    if (this.match('-') && this.peek(1).type === 'number') {
      this.advance();
      const num = this.advance();
      return { type: 'literal', value: -parseFloat(num.value) };
    }
    return this.parsePostfix();
  }

  parsePostfix() {
    let left = this.parsePrimary();
    while (true) {
      if (this.match("'s")) {
        this.advance();
        const next = this.peek();
        const prop = next.type === 'styleProperty' ? this.advance().value : this.advance().value;
        left = { type: 'possessive', object: left, property: prop };
      } else if (this.peek().type === 'styleProperty') {
        const prop = this.advance().value;
        left = { type: 'possessive', object: left, property: prop };
      } else if (this.peek().value === '.') {
        this.advance();
        const prop = this.advance().value;
        left = { type: 'member', object: left, property: prop };
      } else if (this.peek().type === 'selector' && this.peek().value.startsWith('.')) {
        const prop = this.advance().value.slice(1);
        left = { type: 'member', object: left, property: prop };
      } else if (this.peek().value === '(') {
        this.advance();
        const args = [];
        while (!this.match(')')) {
          args.push(this.parseExpression());
          if (this.match(',')) this.advance();
        }
        this.expect(')');
        left = { type: 'call', callee: left, args };
      } else if (this.peek().value === '[' && left.type !== 'selector') {
        this.advance();
        const index = this.parseExpression();
        this.expect(']');
        left = { type: 'member', object: left, property: index, computed: true };
      } else {
        break;
      }
    }
    return left;
  }

  parsePrimary() {
    const token = this.peek();

    if (token.value === '(') {
      this.advance();
      const expr = this.parseExpression();
      this.expect(')');
      return expr;
    }

    if (token.value === '{') return this.parseObjectLiteral();
    if (token.value === '[') return this.parseArrayLiteral();

    if (token.type === 'number') {
      this.advance();
      const val = token.value;
      if (val.endsWith('ms')) return { type: 'literal', value: parseInt(val), unit: 'ms' };
      if (val.endsWith('s')) return { type: 'literal', value: parseFloat(val) * 1000, unit: 'ms' };
      return { type: 'literal', value: parseFloat(val) };
    }

    if (token.type === 'string') {
      this.advance();
      return { type: 'literal', value: token.value.slice(1, -1) };
    }

    if (this.match('true')) { this.advance(); return { type: 'literal', value: true }; }
    if (this.match('false')) { this.advance(); return { type: 'literal', value: false }; }
    if (this.match('null')) { this.advance(); return { type: 'literal', value: null }; }
    if (this.match('undefined')) { this.advance(); return { type: 'literal', value: undefined }; }

    if (token.type === 'localVar') {
      this.advance();
      return { type: 'variable', name: token.value, scope: 'local' };
    }
    if (token.type === 'globalVar') {
      this.advance();
      return { type: 'variable', name: token.value, scope: 'global' };
    }
    if (token.type === 'selector') {
      this.advance();
      return { type: 'selector', value: token.value };
    }

    if (this.match('my')) {
      this.advance();
      const next = this.peek();
      if ((next.type === 'identifier' || next.type === 'keyword') && !this.isCommandKeyword(next)) {
        const prop = this.advance().value;
        return { type: 'possessive', object: { type: 'identifier', value: 'me' }, property: prop };
      }
      return { type: 'identifier', value: 'me' };
    }
    if (this.match('its')) {
      this.advance();
      const next = this.peek();
      if ((next.type === 'identifier' || next.type === 'keyword') && !this.isCommandKeyword(next)) {
        const prop = this.advance().value;
        return { type: 'possessive', object: { type: 'identifier', value: 'it' }, property: prop };
      }
      return { type: 'identifier', value: 'it' };
    }
    if (this.match('me')) { this.advance(); return { type: 'identifier', value: 'me' }; }
    if (this.match('it')) { this.advance(); return { type: 'identifier', value: 'it' }; }
    if (this.match('you')) { this.advance(); return { type: 'identifier', value: 'you' }; }

    if (this.match('the', 'a', 'an')) {
      this.advance();
      if (this.match('first', 'last', 'next', 'previous', 'closest', 'parent')) {
        const position = this.advance().value;
        const target = this.parsePositionalTarget();
        return { type: 'positional', position, target };
      }
      return this.parsePrimary();
    }

    if (this.match('first', 'last', 'next', 'previous', 'closest', 'parent')) {
      const position = this.advance().value;
      const target = this.parsePositionalTarget();
      return { type: 'positional', position, target };
    }

    if (token.type === 'identifier' || token.type === 'keyword') {
      this.advance();
      return { type: 'identifier', value: token.value };
    }

    this.advance();
    return { type: 'identifier', value: token.value };
  }

  parseObjectLiteral() {
    this.expect('{');
    const properties = [];
    while (!this.match('}')) {
      const key = this.advance().value;
      this.expect(':');
      const value = this.parseExpression();
      properties.push({ key, value });
      if (this.match(',')) this.advance();
    }
    this.expect('}');
    return { type: 'object', properties };
  }

  parseArrayLiteral() {
    this.expect('[');
    const elements = [];
    while (!this.match(']')) {
      elements.push(this.parseExpression());
      if (this.match(',')) this.advance();
    }
    this.expect(']');
    return { type: 'array', elements };
  }

  parsePositionalTarget() {
    const token = this.peek();
    if (token.type === 'selector') {
      return { type: 'selector', value: this.advance().value };
    }
    if (token.type === 'identifier' || token.type === 'keyword') {
      return { type: 'identifier', value: this.advance().value };
    }
    return this.parseExpression();
  }
}
`;
function getParserTemplate(type) {
    return type === 'lite' ? LITE_PARSER_TEMPLATE : HYBRID_PARSER_TEMPLATE;
}
const LITE_PARSER_COMMANDS = [
    'toggle',
    'add',
    'remove',
    'put',
    'set',
    'log',
    'send',
    'wait',
    'show',
    'hide',
];
function canUseLiteParser(commands, blocks, positional) {
    if (blocks.length > 0)
        return false;
    if (positional)
        return false;
    const liteCommands = new Set(LITE_PARSER_COMMANDS);
    return commands.every(cmd => liteCommands.has(cmd));
}

const AVAILABLE_COMMANDS = [
    'toggle',
    'add',
    'remove',
    'removeClass',
    'show',
    'hide',
    'put',
    'append',
    'take',
    'set',
    'get',
    'increment',
    'decrement',
    'wait',
    'transition',
    'send',
    'trigger',
    'log',
    'call',
    'copy',
    'beep',
    'go',
    'push',
    'push-url',
    'replace',
    'replace-url',
    'focus',
    'blur',
    'return',
    'break',
    'continue',
    'halt',
    'exit',
    'throw',
    'js',
    'morph',
];
const AVAILABLE_BLOCKS = ['if', 'repeat', 'for', 'while', 'fetch'];
const FULL_RUNTIME_ONLY_COMMANDS = [
    'async',
    'make',
    'swap',
    'process-partials',
    'bind',
    'persist',
    'default',
    'tell',
    'pick',
    'unless',
    'settle',
    'measure',
    'install',
];
function isAvailableCommand(command) {
    return AVAILABLE_COMMANDS.includes(command);
}
function isAvailableBlock(block) {
    return AVAILABLE_BLOCKS.includes(block);
}
function requiresFullRuntime(command) {
    return FULL_RUNTIME_ONLY_COMMANDS.includes(command);
}

exports.AVAILABLE_BLOCKS = AVAILABLE_BLOCKS;
exports.AVAILABLE_COMMANDS = AVAILABLE_COMMANDS;
exports.BLOCK_IMPLEMENTATIONS = BLOCK_IMPLEMENTATIONS;
exports.COMMAND_IMPLEMENTATIONS = COMMAND_IMPLEMENTATIONS;
exports.ELEMENT_ARRAY_COMMANDS = ELEMENT_ARRAY_COMMANDS;
exports.FULL_RUNTIME_ONLY_COMMANDS = FULL_RUNTIME_ONLY_COMMANDS;
exports.HYBRID_PARSER_TEMPLATE = HYBRID_PARSER_TEMPLATE;
exports.LITE_PARSER_COMMANDS = LITE_PARSER_COMMANDS;
exports.LITE_PARSER_TEMPLATE = LITE_PARSER_TEMPLATE;
exports.MORPH_COMMANDS = MORPH_COMMANDS;
exports.STYLE_COMMANDS = STYLE_COMMANDS;
exports.canUseLiteParser = canUseLiteParser;
exports.generateBundle = generateBundle;
exports.generateBundleCode = generateBundleCode;
exports.getAvailableBlocks = getAvailableBlocks;
exports.getAvailableCommands = getAvailableCommands;
exports.getBlockImplementations = getBlockImplementations;
exports.getCommandImplementations = getCommandImplementations;
exports.getParserTemplate = getParserTemplate;
exports.isAvailableBlock = isAvailableBlock;
exports.isAvailableCommand = isAvailableCommand;
exports.requiresFullRuntime = requiresFullRuntime;
//# sourceMappingURL=index.js.map
