// Ported from packages/lecodes-cli/src/compile/assetMacro.ts (itself from the backend). The
// compile-time `asset('./x.png')` macro is desugared into a real import so it goes through the same
// loader/resolver as a hand-written import.

// Matches `asset("./img.png")` / `asset('./img.png')` calls whose argument is a single string
// literal (handling escaped chars inside the literal). The lookbehind excludes property access —
// a METHOD named asset (`ui.asset("model")`, InspectorUI) is not the macro.
const ASSET_RE = /(?<![.\w$])asset\(\s*(["'])((?:[^"'\\]|\\.)*)\1\s*\)/g

/** Cheap pre-check so we don't run the regex over every file. */
export const hasAssetMacro = (src: string): boolean => src.includes("asset(")

/**
 * Desugars `asset('./path.png')` into the exact import the user could have written by hand.
 * Identical paths within a file map to one identifier. Only string-literal arguments are supported.
 */
export const desugarAssetMacros = (src: string, filePath: string): string => {
  const byPath = new Map<string, string>()
  let i = 0

  const out = src.replace(ASSET_RE, (_match, _quote, path) => {
    let name = byPath.get(path)
    if (!name) {
      name = `__asset_${i++}`
      byPath.set(path, name)
    }
    return name
  })

  if (/(?<![.\w$])asset\(/.test(out)) {
    throw new Error(`asset() expects a string literal, found a dynamic argument in: ${filePath}`)
  }

  if (byPath.size === 0) return src

  const imports = [ ...byPath ]
    .map(([ path, name ]) => `import ${name} from ${JSON.stringify(path)};`)
    .join("\n")

  // APPENDED, never prepended. ESM imports hoist, so their position is semantically irrelevant —
  // but lines added at the TOP would shift every original line, and the bundle's source map would
  // then disagree with the file the editor shows: inspector jumps and error-overlay stack frames
  // land N lines off in exactly (and only) the files that use asset(). Appending adds no offsets.
  return out + "\n" + imports
}
