// Compile-time injection of an aspect's runtime name. A user aspect carries its accessor name in the
// (type-erased) generic — `class Health extends Aspect<'health', Sprite> {…}` — so neither the
// bundler nor the runtime can see it after types are stripped. This pass injects the matching
// `static aspect = "health"` so node.aspect()/node.health work at runtime, exactly like the SDK's
// built-in aspects declare it by hand.
//
// Runs on PROJECT files only (the SDK's own aspects already declare `static aspect`). Idempotent:
// skips a class that already declares one. String-based, like assetMacro — no parser dependency.

// `class X extends Aspect<'name', …>` up to the class body's opening brace. Group 3 = the name.
const ASPECT_RE = /\bclass\s+\w+\s+extends\s+Aspect\s*<\s*(["'])((?:[^"'\\]|\\.)*)\1[^{};]*>\s*\{/g

/** Cheap pre-check so we don't scan files that define no aspects. */
export const hasAspectExtends = (src: string): boolean => /\bextends\s+Aspect\s*</.test(src)

/** Inject `static aspect = "<name>"` into each `class … extends Aspect<'<name>', …>`. */
export const injectAspectNames = (src: string): string => {
  let out = ""
  let last = 0
  ASPECT_RE.lastIndex = 0
  let m: RegExpExecArray | null
  while ((m = ASPECT_RE.exec(src)) !== null) {
    const braceEnd = m.index + m[0].length // index just past the opening `{`
    if (!classBodyDeclaresAspect(src, braceEnd)) {
      out += src.slice(last, braceEnd) + ` static aspect = ${JSON.stringify(m[2])};`
      last = braceEnd
    }
  }
  return last === 0 ? src : out + src.slice(last)
}

/** Does the class body starting just after `from` (its `{`) already declare a `static aspect`? */
const classBodyDeclaresAspect = (src: string, from: number): boolean => {
  let depth = 1
  let i = from
  for (; i < src.length && depth > 0; i++) {
    const c = src[i]
    if (c === "{") depth++
    else if (c === "}") depth--
  }
  return /\bstatic\b[^;{}()=]*\baspect\b/.test(src.slice(from, i))
}
