{"version":3,"sources":["../src/util/fs-utils.ts","../src/ast/parser.ts","../src/ast/format.ts"],"names":[],"mappings":"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","file":"ast.mjs","sourcesContent":["// src/util/fs-utils.ts\r\n\r\nimport fs from 'fs';\r\nimport path from 'path';\r\n\r\n/**\r\n * Convert any path to a POSIX-style path with forward slashes.\r\n */\r\nexport function toPosixPath(p: string): string {\r\n   return p.replace(/\\\\/g, '/');\r\n}\r\n\r\n/**\r\n * Ensure a directory exists (like mkdir -p).\r\n * Returns the absolute path of the directory.\r\n */\r\nexport function ensureDirSync(dirPath: string): string {\r\n   if (!fs.existsSync(dirPath)) {\r\n      fs.mkdirSync(dirPath, { recursive: true });\r\n   }\r\n   return dirPath;\r\n}\r\n\r\n/**\r\n * Synchronous check for file or directory existence.\r\n */\r\nexport function existsSync(targetPath: string): boolean {\r\n   return fs.existsSync(targetPath);\r\n}\r\n\r\n/**\r\n * Read a file as UTF-8, returning null if it doesn't exist\r\n * or if an error occurs (no exceptions thrown).\r\n */\r\nexport function readFileSafeSync(filePath: string): string | null {\r\n   try {\r\n      return fs.readFileSync(filePath, 'utf8');\r\n   } catch {\r\n      return null;\r\n   }\r\n}\r\n\r\n/**\r\n * Write a UTF-8 file, creating parent directories if needed.\r\n */\r\nexport function writeFileSafeSync(filePath: string, contents: string): void {\r\n   const dir = path.dirname(filePath);\r\n   ensureDirSync(dir);\r\n   fs.writeFileSync(filePath, contents, 'utf8');\r\n}\r\n\r\n/**\r\n * Remove a file if it exists. Does nothing on error.\r\n */\r\nexport function removeFileSafeSync(filePath: string): void {\r\n   try {\r\n      if (fs.existsSync(filePath)) {\r\n         fs.unlinkSync(filePath);\r\n      }\r\n   } catch {\r\n      // ignore\r\n   }\r\n}\r\n\r\n/**\r\n * Get file stats if they exist, otherwise null.\r\n */\r\nexport function statSafeSync(targetPath: string): fs.Stats | null {\r\n   try {\r\n      return fs.statSync(targetPath);\r\n   } catch {\r\n      return null;\r\n   }\r\n}\r\n\r\n/**\r\n * Resolve an absolute path from projectRoot + relative path,\r\n * and assert it stays within the project root.\r\n *\r\n * Throws if the resolved path escapes the project root.\r\n */\r\nexport function resolveProjectPath(projectRoot: string, relPath: string): string {\r\n   const absRoot = path.resolve(projectRoot);\r\n   const absTarget = path.resolve(absRoot, relPath);\r\n\r\n   // Normalise for safety check\r\n   const rootWithSep = absRoot.endsWith(path.sep) ? absRoot : absRoot + path.sep;\r\n   if (!absTarget.startsWith(rootWithSep) && absTarget !== absRoot) {\r\n      throw new Error(\r\n         `Attempted to resolve path outside project root: ` +\r\n         `root=\"${absRoot}\", target=\"${absTarget}\"`,\r\n      );\r\n   }\r\n\r\n   return absTarget;\r\n}\r\n\r\n/**\r\n * Convert an absolute path back to a project-relative path.\r\n * Throws if the path is not under projectRoot.\r\n */\r\nexport function toProjectRelativePath(projectRoot: string, absolutePath: string): string {\r\n   const absRoot = path.resolve(projectRoot);\r\n   const absTarget = path.resolve(absolutePath);\r\n\r\n   const rootWithSep = absRoot.endsWith(path.sep) ? absRoot : absRoot + path.sep;\r\n   if (!absTarget.startsWith(rootWithSep) && absTarget !== absRoot) {\r\n      throw new Error(\r\n         `Path \"${absTarget}\" is not inside project root \"${absRoot}\".`,\r\n      );\r\n   }\r\n\r\n   const rel = path.relative(absRoot, absTarget);\r\n   return toPosixPath(rel);\r\n}\r\n\r\n/**\r\n * Check if `target` is inside (or equal to) `base` directory.\r\n */\r\nexport function isSubPath(base: string, target: string): boolean {\r\n   const absBase = path.resolve(base);\r\n   const absTarget = path.resolve(target);\r\n\r\n   const baseWithSep = absBase.endsWith(path.sep) ? absBase : absBase + path.sep;\r\n   return absTarget === absBase || absTarget.startsWith(baseWithSep);\r\n}","// src/ast/parser.ts\r\n\r\nimport {toPosixPath} from '../util/fs-utils';\r\n\r\nexport type AstMode = 'strict' | 'loose';\r\n\r\nexport type DiagnosticSeverity = 'info' | 'warning' | 'error';\r\n\r\nexport interface Diagnostic {\r\n    line: number; // 1-based\r\n    column?: number; // 1-based (optional)\r\n    message: string;\r\n    severity: DiagnosticSeverity;\r\n    code?: string;\r\n}\r\n\r\n/**\r\n * How a physical line in the text was classified.\r\n */\r\nexport type LineKind = 'blank' | 'comment' | 'entry';\r\n\r\nexport interface StructureAstLine {\r\n    index: number; // 0-based\r\n    lineNo: number; // 1-based\r\n    raw: string;\r\n    kind: LineKind;\r\n    indentSpaces: number;\r\n    content: string; // after leading whitespace (includes path+annotations+inline comment)\r\n}\r\n\r\n/**\r\n * AST node base for structure entries.\r\n */\r\ninterface AstNodeBase {\r\n    type: 'dir' | 'file';\r\n    /** The last segment name, e.g. \"schema/\" or \"index.ts\". */\r\n    name: string;\r\n    /** Depth level (0 = root, 1 = child of root, etc.). */\r\n    depth: number;\r\n    /** 1-based source line number. */\r\n    line: number;\r\n    /** Normalized POSIX path from root, e.g. \"src/schema/index.ts\" or \"src/schema/\". */\r\n    path: string;\r\n    /** Stub annotation, if any. */\r\n    stub?: string;\r\n    /** Include glob patterns, if any. */\r\n    include?: string[];\r\n    /** Exclude glob patterns, if any. */\r\n    exclude?: string[];\r\n    /** Parent node; null for roots. */\r\n    parent: DirNode | null;\r\n}\r\n\r\nexport interface DirNode extends AstNodeBase {\r\n    type: 'dir';\r\n    children: AstNode[];\r\n}\r\n\r\nexport interface FileNode extends AstNodeBase {\r\n    type: 'file';\r\n    children?: undefined;\r\n}\r\n\r\nexport type AstNode = DirNode | FileNode;\r\n\r\nexport interface AstOptions {\r\n    /**\r\n     * Spaces per indent level.\r\n     * Default: 2.\r\n     */\r\n    indentStep?: number;\r\n\r\n    /**\r\n     * Parser mode:\r\n     * - \"strict\": mismatched indentation / impossible structures are errors.\r\n     * - \"loose\" : tries to recover from bad indentation, demotes some issues to warnings.\r\n     *\r\n     * Default: \"loose\".\r\n     */\r\n    mode?: AstMode;\r\n}\r\n\r\n/**\r\n * Full AST result: nodes + per-line meta + diagnostics.\r\n */\r\nexport interface StructureAst {\r\n    /** Root-level nodes (depth 0). */\r\n    rootNodes: AstNode[];\r\n    /** All lines as seen in the source file. */\r\n    lines: StructureAstLine[];\r\n    /** Collected diagnostics (errors + warnings + infos). */\r\n    diagnostics: Diagnostic[];\r\n    /** Resolved options used by the parser. */\r\n    options: Required<AstOptions>;\r\n}\r\n\r\n/**\r\n * Main entry: parse a structure text into an AST tree with diagnostics.\r\n *\r\n * - Does NOT throw on parse errors.\r\n * - Always returns something (even if diagnostics contain errors).\r\n * - In \"loose\" mode, attempts to repair:\r\n *   - odd/misaligned indentation → snapped via relative depth rules with warnings.\r\n *   - large indent jumps → treated as \"one level deeper\" with warnings.\r\n *   - children under files → attached to nearest viable ancestor with warnings.\r\n */\r\nexport function parseStructureAst(\r\n    text: string,\r\n    opts: AstOptions = {},\r\n): StructureAst {\r\n    const indentStep = opts.indentStep ?? 2;\r\n    const mode: AstMode = opts.mode ?? 'loose';\r\n\r\n    const diagnostics: Diagnostic[] = [];\r\n    const lines: StructureAstLine[] = [];\r\n\r\n    const rawLines = text.split(/\\r?\\n/);\r\n\r\n    // First pass: classify + measure indentation.\r\n    for (let i = 0; i < rawLines.length; i++) {\r\n        const raw = rawLines[i];\r\n        const lineNo = i + 1;\r\n\r\n        const m = raw.match(/^(\\s*)(.*)$/);\r\n        const indentRaw = m ? m[1] : '';\r\n        const content = m ? m[2] : '';\r\n\r\n        const {indentSpaces, hasTabs} = measureIndent(indentRaw, indentStep);\r\n\r\n        if (hasTabs) {\r\n            diagnostics.push({\r\n                line: lineNo,\r\n                message:\r\n                    'Tabs detected in indentation. Consider using spaces only for consistent levels.',\r\n                severity: mode === 'strict' ? 'warning' : 'info',\r\n                code: 'indent-tabs',\r\n            });\r\n        }\r\n\r\n        const trimmed = content.trim();\r\n        let kind: LineKind;\r\n        if (!trimmed) {\r\n            kind = 'blank';\r\n        } else if (trimmed.startsWith('#') || trimmed.startsWith('//')) {\r\n            kind = 'comment';\r\n        } else {\r\n            kind = 'entry';\r\n        }\r\n\r\n        lines.push({\r\n            index: i,\r\n            lineNo,\r\n            raw,\r\n            kind,\r\n            indentSpaces,\r\n            content,\r\n        });\r\n    }\r\n\r\n    const rootNodes: AstNode[] = [];\r\n    const stack: AstNode[] = []; // nodes by depth index (0 = level 0, 1 = level 1, ...)\r\n\r\n    const depthCtx: DepthContext = {\r\n        lastIndentSpaces: null,\r\n        lastDepth: null,\r\n        lastWasFile: false,\r\n    };\r\n\r\n    for (const line of lines) {\r\n        if (line.kind !== 'entry') continue;\r\n\r\n        const {entry, depth, diags} = parseEntryLine(\r\n            line,\r\n            indentStep,\r\n            mode,\r\n            depthCtx,\r\n        );\r\n        diagnostics.push(...diags);\r\n\r\n        if (!entry) {\r\n            continue;\r\n        }\r\n\r\n        attachNode(entry, depth, line, rootNodes, stack, diagnostics, mode);\r\n        depthCtx.lastWasFile = !entry.isDir;\r\n    }\r\n\r\n    return {\r\n        rootNodes,\r\n        lines,\r\n        diagnostics,\r\n        options: {\r\n            indentStep,\r\n            mode,\r\n        },\r\n    };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal: indentation measurement & depth fixing (relative model)\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction measureIndent(rawIndent: string, indentStep: number): {\r\n    indentSpaces: number;\r\n    hasTabs: boolean;\r\n} {\r\n    let spaces = 0;\r\n    let hasTabs = false;\r\n\r\n    for (const ch of rawIndent) {\r\n        if (ch === ' ') {\r\n            spaces += 1;\r\n        } else if (ch === '\\t') {\r\n            hasTabs = true;\r\n            // Treat tab as one level to avoid chaos. This is arbitrary but stable-ish.\r\n            spaces += indentStep;\r\n        }\r\n    }\r\n\r\n    return {indentSpaces: spaces, hasTabs};\r\n}\r\n\r\ninterface DepthContext {\r\n    lastIndentSpaces: number | null;\r\n    lastDepth: number | null;\r\n    lastWasFile: boolean;\r\n}\r\n\r\n/**\r\n * Compute logical depth using a relative algorithm:\r\n *\r\n * First entry line:\r\n *   - depth = 0\r\n *\r\n * For each subsequent entry line:\r\n *   Let prevSpaces = lastIndentSpaces, prevDepth = lastDepth.\r\n *\r\n *   - if spaces > prevSpaces:\r\n *       - if spaces > prevSpaces + indentStep → warn about a \"skip\"\r\n *       - depth = prevDepth + 1\r\n *\r\n *   - else if spaces === prevSpaces:\r\n *       - depth = prevDepth\r\n *\r\n *   - else (spaces < prevSpaces):\r\n *       - diff = prevSpaces - spaces\r\n *       - steps = round(diff / indentStep)\r\n *       - if diff is not a clean multiple → warn about misalignment\r\n *       - depth = max(prevDepth - steps, 0)\r\n */\r\nfunction computeDepth(\r\n    line: StructureAstLine,\r\n    indentStep: number,\r\n    mode: AstMode,\r\n    ctx: DepthContext,\r\n    diagnostics: Diagnostic[],\r\n): number {\r\n    let spaces = line.indentSpaces;\r\n    if (spaces < 0) spaces = 0;\r\n\r\n    let depth: number;\r\n\r\n    if (ctx.lastIndentSpaces == null || ctx.lastDepth == null) {\r\n        // First entry line: treat as root.\r\n        depth = 0;\r\n    } else {\r\n        const prevSpaces = ctx.lastIndentSpaces;\r\n        const prevDepth = ctx.lastDepth;\r\n\r\n        if (spaces > prevSpaces) {\r\n            const diff = spaces - prevSpaces;\r\n\r\n            // NEW: indenting under a file → child-of-file-loose\r\n            if (ctx.lastWasFile) {\r\n                diagnostics.push({\r\n                    line: line.lineNo,\r\n                    message:\r\n                        'Entry appears indented under a file; treating it as a sibling of the file instead of a child.',\r\n                    severity: mode === 'strict' ? 'error' : 'warning',\r\n                    code: 'child-of-file-loose',\r\n                });\r\n\r\n                // Treat as sibling of the file, not a child:\r\n                depth = prevDepth;\r\n            } else {\r\n                if (diff > indentStep) {\r\n                    diagnostics.push({\r\n                        line: line.lineNo,\r\n                        message: `Indentation jumps from ${prevSpaces} to ${spaces} spaces; treating as one level deeper.`,\r\n                        severity: mode === 'strict' ? 'error' : 'warning',\r\n                        code: 'indent-skip-level',\r\n                    });\r\n                }\r\n                depth = prevDepth + 1;\r\n            }\r\n        } else if (spaces === prevSpaces) {\r\n            depth = prevDepth;\r\n        } else {\r\n            const diff = prevSpaces - spaces;\r\n            const steps = Math.round(diff / indentStep);\r\n\r\n            if (diff % indentStep !== 0) {\r\n                diagnostics.push({\r\n                    line: line.lineNo,\r\n                    message: `Indentation decreases from ${prevSpaces} to ${spaces} spaces, which is not a multiple of indent step (${indentStep}).`,\r\n                    severity: mode === 'strict' ? 'error' : 'warning',\r\n                    code: 'indent-misaligned',\r\n                });\r\n            }\r\n\r\n            depth = Math.max(prevDepth - steps, 0);\r\n        }\r\n    }\r\n\r\n    ctx.lastIndentSpaces = spaces;\r\n    ctx.lastDepth = depth;\r\n\r\n    return depth;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal: entry line parsing (path + annotations)\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface ParsedEntry {\r\n    segmentName: string;\r\n    isDir: boolean;\r\n    stub?: string;\r\n    include?: string[];\r\n    exclude?: string[];\r\n}\r\n\r\n/**\r\n * Parse a single entry line into a ParsedEntry + depth.\r\n */\r\nfunction parseEntryLine(\r\n    line: StructureAstLine,\r\n    indentStep: number,\r\n    mode: AstMode,\r\n    ctx: DepthContext,\r\n): {\r\n    entry: ParsedEntry | null;\r\n    depth: number;\r\n    diags: Diagnostic[];\r\n} {\r\n    const diags: Diagnostic[] = [];\r\n    const depth = computeDepth(line, indentStep, mode, ctx, diags);\r\n\r\n    // Extract before inline comment\r\n    const {contentWithoutComment} = extractInlineCommentParts(line.content);\r\n    const trimmed = contentWithoutComment.trim();\r\n    if (!trimmed) {\r\n        // Structural line that became empty after stripping inline comment; treat as no-op.\r\n        return {entry: null, depth, diags};\r\n    }\r\n\r\n    const parts = trimmed.split(/\\s+/);\r\n    const pathToken = parts[0];\r\n    const annotationTokens = parts.slice(1);\r\n\r\n    // Path sanity checks\r\n    if (pathToken.includes(':')) {\r\n        diags.push({\r\n            line: line.lineNo,\r\n            message:\r\n                'Path token contains \":\" which is reserved for annotations. This is likely a mistake.',\r\n            severity: mode === 'strict' ? 'error' : 'warning',\r\n            code: 'path-colon',\r\n        });\r\n    }\r\n\r\n    const isDir = pathToken.endsWith('/');\r\n    const segmentName = pathToken;\r\n\r\n    let stub: string | undefined;\r\n    const include: string[] = [];\r\n    const exclude: string[] = [];\r\n\r\n    for (const token of annotationTokens) {\r\n        if (token.startsWith('@stub:')) {\r\n            stub = token.slice('@stub:'.length);\r\n        } else if (token.startsWith('@include:')) {\r\n            const val = token.slice('@include:'.length);\r\n            if (val) {\r\n                include.push(\r\n                    ...val\r\n                        .split(',')\r\n                        .map((s) => s.trim())\r\n                        .filter(Boolean),\r\n                );\r\n            }\r\n        } else if (token.startsWith('@exclude:')) {\r\n            const val = token.slice('@exclude:'.length);\r\n            if (val) {\r\n                exclude.push(\r\n                    ...val\r\n                        .split(',')\r\n                        .map((s) => s.trim())\r\n                        .filter(Boolean),\r\n                );\r\n            }\r\n        } else if (token.startsWith('@')) {\r\n            diags.push({\r\n                line: line.lineNo,\r\n                message: `Unknown annotation token \"${token}\".`,\r\n                severity: 'info',\r\n                code: 'unknown-annotation',\r\n            });\r\n        }\r\n    }\r\n\r\n    const entry: ParsedEntry = {\r\n        segmentName,\r\n        isDir,\r\n        stub,\r\n        include: include.length ? include : undefined,\r\n        exclude: exclude.length ? exclude : undefined,\r\n    };\r\n\r\n    return {entry, depth, diags};\r\n}\r\n\r\nexport function mapThrough(content: string) {\r\n    let cutIndex = -1;\r\n    const len = content.length;\r\n\r\n    for (let i = 0; i < len; i++) {\r\n        const ch = content[i];\r\n        const prev = i > 0 ? content[i - 1] : '';\r\n\r\n        // Inline \"# ...\"\r\n        if (ch === '#') {\r\n            if (i === 0) {\r\n                // full-line comment; not our case (we only call this for \"entry\" lines)\r\n                continue;\r\n            }\r\n            if (prev === ' ' || prev === '\\t') {\r\n                cutIndex = i;\r\n                break;\r\n            }\r\n        }\r\n\r\n        // Inline \"// ...\"\r\n        if (\r\n            ch === '/' &&\r\n            i + 1 < len &&\r\n            content[i + 1] === '/' &&\r\n            (prev === ' ' || prev === '\\t')\r\n        ) {\r\n            cutIndex = i;\r\n            break;\r\n        }\r\n    }\r\n\r\n    return cutIndex;\r\n}\r\n\r\n/**\r\n * Extracts the inline comment portion (if any) from the content area (no leading indent).\r\n */\r\nexport function extractInlineCommentParts(content: string): {\r\n    contentWithoutComment: string;\r\n    inlineComment: string | null;\r\n} {\r\n    const cutIndex = mapThrough(content);\r\n\r\n    if (cutIndex === -1) {\r\n        return {\r\n            contentWithoutComment: content,\r\n            inlineComment: null,\r\n        };\r\n    }\r\n\r\n    return {\r\n        contentWithoutComment: content.slice(0, cutIndex),\r\n        inlineComment: content.slice(cutIndex),\r\n    };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal: tree construction\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction attachNode(\r\n    entry: ParsedEntry,\r\n    depth: number,\r\n    line: StructureAstLine,\r\n    rootNodes: AstNode[],\r\n    stack: AstNode[],\r\n    diagnostics: Diagnostic[],\r\n    mode: AstMode,\r\n): void {\r\n    const lineNo = line.lineNo;\r\n\r\n    // Pop stack until we’re at or above the desired depth.\r\n    while (stack.length > depth) {\r\n        stack.pop();\r\n    }\r\n\r\n    let parent: DirNode | null = null;\r\n    if (depth > 0) {\r\n        const candidate = stack[depth - 1];\r\n        if (!candidate) {\r\n            // Indented but no parent; in strict mode error, in loose mode, treat as root.\r\n            diagnostics.push({\r\n                line: lineNo,\r\n                message: `Entry has indent depth ${depth} but no parent at depth ${\r\n                    depth - 1\r\n                }. Treating as root.`,\r\n                severity: mode === 'strict' ? 'error' : 'warning',\r\n                code: 'missing-parent',\r\n            });\r\n        } else if (candidate.type === 'file') {\r\n            // Child under file, impossible by design.\r\n            if (mode === 'strict') {\r\n                diagnostics.push({\r\n                    line: lineNo,\r\n                    message: `Cannot attach child under file \"${candidate.path}\".`,\r\n                    severity: 'error',\r\n                    code: 'child-of-file',\r\n                });\r\n                // Force it to root to at least keep the node.\r\n            } else {\r\n                diagnostics.push({\r\n                    line: lineNo,\r\n                    message: `Entry appears under file \"${candidate.path}\". Attaching as sibling at depth ${\r\n                        candidate.depth\r\n                    }.`,\r\n                    severity: 'warning',\r\n                    code: 'child-of-file-loose',\r\n                });\r\n                // Treat as sibling at candidate's depth.\r\n                while (stack.length > candidate.depth) {\r\n                    stack.pop();\r\n                }\r\n            }\r\n        } else {\r\n            parent = candidate as DirNode;\r\n        }\r\n    }\r\n\r\n    const parentPath = parent ? parent.path.replace(/\\/$/, '') : '';\r\n    const normalizedSegment = toPosixPath(entry.segmentName.replace(/\\/+$/, ''));\r\n    const fullPath = parentPath\r\n        ? `${parentPath}/${normalizedSegment}${entry.isDir ? '/' : ''}`\r\n        : `${normalizedSegment}${entry.isDir ? '/' : ''}`;\r\n\r\n    const baseNode: AstNodeBase = {\r\n        type: entry.isDir ? 'dir' : 'file',\r\n        name: entry.segmentName,\r\n        depth,\r\n        line: lineNo,\r\n        path: fullPath,\r\n        parent,\r\n        ...(entry.stub ? {stub: entry.stub} : {}),\r\n        ...(entry.include ? {include: entry.include} : {}),\r\n        ...(entry.exclude ? {exclude: entry.exclude} : {}),\r\n    };\r\n\r\n    if (entry.isDir) {\r\n        const dirNode: DirNode = {\r\n            ...baseNode,\r\n            type: 'dir',\r\n            children: [],\r\n        };\r\n\r\n        if (parent) {\r\n            parent.children.push(dirNode);\r\n        } else {\r\n            rootNodes.push(dirNode);\r\n        }\r\n\r\n        // Ensure stack[depth] is this dir.\r\n        while (stack.length > depth) {\r\n            stack.pop();\r\n        }\r\n        stack[depth] = dirNode;\r\n    } else {\r\n        const fileNode: FileNode = {\r\n            ...baseNode,\r\n            type: 'file',\r\n        };\r\n\r\n        if (parent) {\r\n            parent.children.push(fileNode);\r\n        } else {\r\n            rootNodes.push(fileNode);\r\n        }\r\n\r\n        // Files themselves are NOT placed on the stack to prevent children,\r\n        // but attachNode will repair children-under-file in loose mode.\r\n    }\r\n}","// src/ast/format.ts\r\n\r\nimport {\r\n    parseStructureAst,\r\n    type AstMode,\r\n    type StructureAst,\r\n    type AstNode, extractInlineCommentParts,\r\n} from './parser';\r\nimport {FormatConfig} from \"../schema\";\r\n\r\nexport interface FormatOptions extends FormatConfig {\r\n    /**\r\n     * Spaces per indent level for re-printing entries.\r\n     * Defaults to 2.\r\n     */\r\n    indentStep?: number;\r\n\r\n    /**\r\n     * Parser mode to use for the AST.\r\n     * - \"loose\": attempt to repair mis-indents / bad parents (default).\r\n     * - \"strict\": report issues as errors, less repair.\r\n     */\r\n    mode?: AstMode;\r\n\r\n    /**\r\n     * Normalize newlines to the dominant style in the original text (LF vs. CRLF).\r\n     * Defaults to true.\r\n     */\r\n    normalizeNewlines?: boolean;\r\n\r\n    /**\r\n     * Trim trailing whitespace on non-entry lines (comments / blanks).\r\n     * Defaults to true.\r\n     */\r\n    trimTrailingWhitespace?: boolean;\r\n\r\n    /**\r\n     * Whether to normalize annotation ordering and spacing:\r\n     *   name @stub:... @include:... @exclude:...\r\n     * Defaults to true.\r\n     */\r\n    normalizeAnnotations?: boolean;\r\n}\r\n\r\nexport interface FormatResult {\r\n    /** Formatted text. */\r\n    text: string;\r\n    /** Underlying AST that was used. */\r\n    ast: StructureAst;\r\n}\r\n\r\n/**\r\n * Smart formatter for scaffold structure files.\r\n *\r\n * - Uses the loose AST parser (parseStructureAst) to understand structure.\r\n * - Auto-fixes indentation based on tree depth (indentStep).\r\n * - Keeps **all** blank lines and full-line comments in place.\r\n * - Preserves inline comments (# / //) on entry lines.\r\n * - Canonicalizes annotation order (stub → include → exclude) if enabled.\r\n *\r\n * It does **not** throw on invalid input:\r\n * - parseStructureAst always returns an AST + diagnostics.\r\n * - If something is catastrophically off (entry/node counts mismatch),\r\n *   it falls back to a minimal normalization pass.\r\n */\r\nexport function formatStructureText(\r\n    text: string,\r\n    options: FormatOptions = {},\r\n): FormatResult {\r\n    const indentStep = options.indentStep ?? 2;\r\n    const mode: AstMode = options.mode ?? 'loose';\r\n    const normalizeNewlines =\r\n        options.normalizeNewlines === undefined ? true : options.normalizeNewlines;\r\n    const trimTrailingWhitespace =\r\n        options.trimTrailingWhitespace === undefined\r\n            ? true\r\n            : options.trimTrailingWhitespace;\r\n    const normalizeAnnotations =\r\n        options.normalizeAnnotations === undefined\r\n            ? true\r\n            : options.normalizeAnnotations;\r\n\r\n    // 1. Parse to our \"smart\" AST (non-throwing).\r\n    const ast = parseStructureAst(text, {\r\n        indentStep,\r\n        mode,\r\n    });\r\n\r\n    const rawLines = text.split(/\\r?\\n/);\r\n    const lineCount = rawLines.length;\r\n\r\n    // Sanity check: AST lines length should match raw lines length.\r\n    if (ast.lines.length !== lineCount) {\r\n        return {\r\n            text: basicNormalize(text, {normalizeNewlines, trimTrailingWhitespace}),\r\n            ast,\r\n        };\r\n    }\r\n\r\n    // 2. Collect entry line indices and inline comments from the original text.\r\n    const entryLineIndexes: number[] = [];\r\n    const inlineComments: (string | null)[] = [];\r\n\r\n    for (let i = 0; i < lineCount; i++) {\r\n        const lineMeta = ast.lines[i];\r\n        if (lineMeta.kind === 'entry') {\r\n            entryLineIndexes.push(i);\r\n            const {inlineComment} = extractInlineCommentParts(lineMeta.content);\r\n            inlineComments.push(inlineComment);\r\n        }\r\n    }\r\n\r\n    // 3. Flatten AST nodes in depth-first order to get an ordered node list.\r\n    const flattened: { node: AstNode; level: number }[] = [];\r\n    flattenAstNodes(ast.rootNodes, 0, flattened);\r\n\r\n    if (flattened.length !== entryLineIndexes.length) {\r\n        // If counts don't match, something is inconsistent – do not risk corruption.\r\n        return {\r\n            text: basicNormalize(text, {normalizeNewlines, trimTrailingWhitespace}),\r\n            ast,\r\n        };\r\n    }\r\n\r\n    // 4. Build canonical entry lines from AST nodes.\r\n    const canonicalEntryLines: string[] = flattened.map(({node, level}) =>\r\n        formatAstNodeLine(node, level, indentStep, normalizeAnnotations),\r\n    );\r\n\r\n    // 5. Merge canonical entry lines + inline comments back into original structure.\r\n    const resultLines: string[] = [];\r\n    let entryIdx = 0;\r\n\r\n    for (let i = 0; i < lineCount; i++) {\r\n        const lineMeta = ast.lines[i];\r\n        const originalLine = rawLines[i];\r\n\r\n        if (lineMeta.kind === 'entry') {\r\n            const base = canonicalEntryLines[entryIdx].replace(/[ \\t]+$/g, '');\r\n            const inline = inlineComments[entryIdx];\r\n            entryIdx++;\r\n\r\n            if (inline) {\r\n                // Always ensure a single space before the inline comment marker.\r\n                resultLines.push(base + ' ' + inline);\r\n            } else {\r\n                resultLines.push(base);\r\n            }\r\n        } else {\r\n            let out = originalLine;\r\n            if (trimTrailingWhitespace) {\r\n                out = out.replace(/[ \\t]+$/g, '');\r\n            }\r\n            resultLines.push(out);\r\n        }\r\n    }\r\n\r\n    const eol = normalizeNewlines ? detectPreferredEol(text) : getRawEol(text);\r\n    return {\r\n        text: resultLines.join(eol),\r\n        ast,\r\n    };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Fallback: basic normalization when we can't safely map AST ↔ text.\r\n */\r\nfunction basicNormalize(\r\n    text: string,\r\n    opts: { normalizeNewlines: boolean; trimTrailingWhitespace: boolean },\r\n): string {\r\n    const lines = text.split(/\\r?\\n/);\r\n    const normalizedLines = opts.trimTrailingWhitespace\r\n        ? lines.map((line) => line.replace(/[ \\t]+$/g, ''))\r\n        : lines;\r\n\r\n    const eol = opts.normalizeNewlines ? detectPreferredEol(text) : getRawEol(text);\r\n    return normalizedLines.join(eol);\r\n}\r\n\r\n/**\r\n * Detect whether the file is more likely LF or CRLF and reuse that.\r\n * If mixed or no clear signal, default to \"\\n\".\r\n */\r\nfunction detectPreferredEol(text: string): string {\r\n    const crlfCount = (text.match(/\\r\\n/g) || []).length;\r\n    const lfCount = (text.match(/(?<!\\r)\\n/g) || []).length;\r\n\r\n    if (crlfCount === 0 && lfCount === 0) {\r\n        return '\\n';\r\n    }\r\n\r\n    if (crlfCount > lfCount) {\r\n        return '\\r\\n';\r\n    }\r\n\r\n    return '\\n';\r\n}\r\n\r\n/**\r\n * If you really want the raw style, detect only CRLF vs. LF.\r\n */\r\nfunction getRawEol(text: string): string {\r\n    return text.includes('\\r\\n') ? '\\r\\n' : '\\n';\r\n}\r\n\r\n/**\r\n * Flatten AST nodes into a depth-first list while tracking indent level.\r\n */\r\nfunction flattenAstNodes(\r\n    nodes: AstNode[],\r\n    level: number,\r\n    out: { node: AstNode; level: number }[],\r\n): void {\r\n    for (const node of nodes) {\r\n        out.push({node, level});\r\n        if (node.type === 'dir' && node.children && node.children.length) {\r\n            flattenAstNodes(node.children, level + 1, out);\r\n        }\r\n    }\r\n}\r\n\r\n/**\r\n * Format a single AST node into one canonical line.\r\n *\r\n * - Uses `level * indentStep` spaces as indentation.\r\n * - Uses the node's `name` as provided by the parser (e.g. \"src/\" or \"index.ts\").\r\n * - Annotations are printed in a stable order if normalizeAnnotations is true:\r\n *   @stub:..., @include:..., @exclude:...\r\n */\r\nfunction formatAstNodeLine(\r\n    node: AstNode,\r\n    level: number,\r\n    indentStep: number,\r\n    normalizeAnnotations: boolean,\r\n): string {\r\n    const indent = ' '.repeat(indentStep * level);\r\n    const baseName = node.name;\r\n\r\n    if (!normalizeAnnotations) {\r\n        return indent + baseName;\r\n    }\r\n\r\n    const tokens: string[] = [];\r\n\r\n    if (node.stub) {\r\n        tokens.push(`@stub:${node.stub}`);\r\n    }\r\n    if (node.include && node.include.length > 0) {\r\n        tokens.push(`@include:${node.include.join(',')}`);\r\n    }\r\n    if (node.exclude && node.exclude.length > 0) {\r\n        tokens.push(`@exclude:${node.exclude.join(',')}`);\r\n    }\r\n\r\n    const annotations = tokens.length ? ' ' + tokens.join(' ') : '';\r\n    return indent + baseName + annotations;\r\n}"]}