{"version":3,"file":"indexer-backend-CdvCJCkD.mjs","names":[],"sources":["../src/memory/indexer/async.ts"],"sourcesContent":["/**\n * Backend-aware async indexer for PAI federation memory.\n *\n * Provides the same functionality as sync.ts but writes through the\n * StorageBackend interface instead of directly to better-sqlite3.\n * Used when the daemon is configured with the Postgres backend.\n *\n * The SQLite path still uses sync.ts directly (which is faster for SQLite\n * due to synchronous transactions).\n */\n\nimport { readFileSync, statSync, existsSync } from \"node:fs\";\nimport { join, relative, basename } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend, ChunkRow } from \"../../storage/interface.js\";\nimport { chunkMarkdown } from \"../chunker.js\";\nimport {\n  sha256File,\n  chunkId,\n  detectTier,\n  walkMdFiles,\n  walkContentFiles,\n  isPathTooBroadForContentScan,\n  parseSessionTitleChunk,\n  yieldToEventLoop,\n  INDEX_YIELD_EVERY,\n} from \"./helpers.js\";\nimport type { IndexResult } from \"./types.js\";\n\nexport type { IndexResult };\n\n// ---------------------------------------------------------------------------\n// Single-file indexing via StorageBackend\n// ---------------------------------------------------------------------------\n\n/**\n * Index a single file through the StorageBackend interface.\n * Returns true if the file was re-indexed (changed or new), false if skipped.\n */\nexport async function indexFileWithBackend(\n  backend: StorageBackend,\n  projectId: number,\n  rootPath: string,\n  relativePath: string,\n  source: string,\n  tier: string,\n): Promise<boolean> {\n  const absPath = join(rootPath, relativePath);\n\n  let content: string;\n  let stat: ReturnType<typeof statSync>;\n  try {\n    content = readFileSync(absPath, \"utf8\");\n    stat = statSync(absPath);\n  } catch {\n    return false;\n  }\n\n  const hash = sha256File(content);\n  const mtime = Math.floor(stat.mtimeMs);\n  const size = stat.size;\n\n  // Change detection\n  const existingHash = await backend.getFileHash(projectId, relativePath);\n  if (existingHash === hash) return false;\n\n  // Delete old chunks\n  await backend.deleteChunksForFile(projectId, relativePath);\n\n  // Chunk the content\n  const rawChunks = chunkMarkdown(content);\n  const updatedAt = Date.now();\n\n  const chunks: ChunkRow[] = rawChunks.map((c, i) => ({\n    id: chunkId(projectId, relativePath, i, c.startLine, c.endLine),\n    projectId,\n    source,\n    tier,\n    path: relativePath,\n    startLine: c.startLine,\n    endLine: c.endLine,\n    hash: c.hash,\n    text: c.text,\n    updatedAt,\n    embedding: null,\n  }));\n\n  // Insert chunks + update file record\n  await backend.insertChunks(chunks);\n  await backend.upsertFile({ projectId, path: relativePath, source, tier, hash, mtime, size });\n\n  return true;\n}\n\n// ---------------------------------------------------------------------------\n// Project-level indexing via StorageBackend\n// ---------------------------------------------------------------------------\n\nexport async function indexProjectWithBackend(\n  backend: StorageBackend,\n  projectId: number,\n  rootPath: string,\n  claudeNotesDir?: string | null,\n): Promise<IndexResult> {\n  const result: IndexResult = { filesProcessed: 0, chunksCreated: 0, filesSkipped: 0 };\n\n  const filesToIndex: Array<{ absPath: string; rootBase: string; source: string; tier: string }> = [];\n\n  const rootMemoryMd = join(rootPath, \"MEMORY.md\");\n  if (existsSync(rootMemoryMd)) {\n    filesToIndex.push({ absPath: rootMemoryMd, rootBase: rootPath, source: \"memory\", tier: \"evergreen\" });\n  }\n\n  const memoryDir = join(rootPath, \"memory\");\n  for (const absPath of walkMdFiles(memoryDir)) {\n    const relPath = relative(rootPath, absPath);\n    const tier = detectTier(relPath);\n    filesToIndex.push({ absPath, rootBase: rootPath, source: \"memory\", tier });\n  }\n\n  const notesDir = join(rootPath, \"Notes\");\n  for (const absPath of walkMdFiles(notesDir)) {\n    filesToIndex.push({ absPath, rootBase: rootPath, source: \"notes\", tier: \"session\" });\n  }\n\n  // Synthetic session-title chunks for Notes files\n  {\n    const updatedAt = Date.now();\n    for (const absPath of walkMdFiles(notesDir)) {\n      const fileName = basename(absPath);\n      const text = parseSessionTitleChunk(fileName);\n      if (!text) continue;\n      const relPath = relative(rootPath, absPath);\n      const syntheticPath = `${relPath}::title`;\n      const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n      const hash = sha256File(text);\n      const titleChunk: ChunkRow = {\n        id, projectId, source: \"notes\", tier: \"session\",\n        path: syntheticPath, startLine: 0, endLine: 0,\n        hash, text, updatedAt, embedding: null,\n      };\n      try {\n        await backend.insertChunks([titleChunk]);\n      } catch {\n        // Skip title chunks that cause backend errors\n      }\n    }\n  }\n\n  if (!isPathTooBroadForContentScan(rootPath)) {\n    for (const absPath of walkContentFiles(rootPath)) {\n      filesToIndex.push({ absPath, rootBase: rootPath, source: \"content\", tier: \"topic\" });\n    }\n  }\n\n  if (claudeNotesDir && claudeNotesDir !== notesDir) {\n    for (const absPath of walkMdFiles(claudeNotesDir)) {\n      filesToIndex.push({ absPath, rootBase: claudeNotesDir, source: \"notes\", tier: \"session\" });\n    }\n\n    // Synthetic title chunks for claude notes dir\n    {\n      const updatedAt = Date.now();\n      for (const absPath of walkMdFiles(claudeNotesDir)) {\n        const fileName = basename(absPath);\n        const text = parseSessionTitleChunk(fileName);\n        if (!text) continue;\n        const relPath = relative(claudeNotesDir, absPath);\n        const syntheticPath = `${relPath}::title`;\n        const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n        const hash = sha256File(text);\n        const titleChunk: ChunkRow = {\n          id, projectId, source: \"notes\", tier: \"session\",\n          path: syntheticPath, startLine: 0, endLine: 0,\n          hash, text, updatedAt, embedding: null,\n        };\n        try {\n          await backend.insertChunks([titleChunk]);\n        } catch {\n          // Skip title chunks that cause backend errors\n        }\n      }\n    }\n\n    if (claudeNotesDir.endsWith(\"/Notes\")) {\n      const claudeProjectDir = claudeNotesDir.slice(0, -\"/Notes\".length);\n      const claudeMemoryMd = join(claudeProjectDir, \"MEMORY.md\");\n      if (existsSync(claudeMemoryMd)) {\n        filesToIndex.push({ absPath: claudeMemoryMd, rootBase: claudeProjectDir, source: \"memory\", tier: \"evergreen\" });\n      }\n      const claudeMemoryDir = join(claudeProjectDir, \"memory\");\n      for (const absPath of walkMdFiles(claudeMemoryDir)) {\n        const relPath = relative(claudeProjectDir, absPath);\n        const tier = detectTier(relPath);\n        filesToIndex.push({ absPath, rootBase: claudeProjectDir, source: \"memory\", tier });\n      }\n    }\n  }\n\n  await yieldToEventLoop();\n\n  let filesSinceYield = 0;\n\n  for (const { absPath, rootBase, source, tier } of filesToIndex) {\n    if (filesSinceYield >= INDEX_YIELD_EVERY) {\n      await yieldToEventLoop();\n      filesSinceYield = 0;\n    }\n    filesSinceYield++;\n\n    const relPath = relative(rootBase, absPath);\n    try {\n      const changed = await indexFileWithBackend(backend, projectId, rootBase, relPath, source, tier);\n\n      if (changed) {\n        const ids = await backend.getChunkIds(projectId, relPath);\n        result.filesProcessed++;\n        result.chunksCreated += ids.length;\n      } else {\n        result.filesSkipped++;\n      }\n    } catch {\n      // Skip files that cause backend errors (e.g. null bytes in Postgres)\n      result.filesSkipped++;\n    }\n  }\n\n  // Prune stale paths\n  const livePaths = new Set<string>();\n  for (const { absPath, rootBase } of filesToIndex) {\n    livePaths.add(relative(rootBase, absPath));\n  }\n\n  const dbChunkPaths = await backend.getDistinctChunkPaths(projectId);\n\n  const stalePaths: string[] = [];\n  for (const p of dbChunkPaths) {\n    const basePath = p.endsWith(\"::title\") ? p.slice(0, -\"::title\".length) : p;\n    if (!livePaths.has(basePath)) {\n      stalePaths.push(p);\n    }\n  }\n\n  if (stalePaths.length > 0) {\n    await backend.deletePaths(projectId, stalePaths);\n  }\n\n  return result;\n}\n\n// ---------------------------------------------------------------------------\n// Embedding generation via StorageBackend\n// ---------------------------------------------------------------------------\n\nconst EMBED_BATCH_SIZE = 50;\nconst EMBED_YIELD_EVERY = 1;\n\n/** Default ceiling on one pass. See EmbedPassOptions. */\nconst DEFAULT_MAX_CHUNKS_PER_PASS = 5_000;\n/** Default wall-clock ceiling on one pass, in milliseconds. */\nconst DEFAULT_MAX_MILLIS_PER_PASS = 120_000;\n\nexport interface EmbedPassOptions {\n  /**\n   * Stop the pass after this many chunks. Unbounded passes are the reason a\n   * six-figure backlog starves the indexer: the daemon serialises indexing and\n   * embedding against each other, so a pass that runs for hours means nothing\n   * new gets indexed for hours. Bounding the pass costs nothing — every\n   * embedding is written as it is produced, so the next pass resumes exactly\n   * where this one stopped.\n   */\n  maxChunks?: number;\n  /** Stop the pass after this much wall-clock time, whichever bound hits first. */\n  maxMillis?: number;\n}\n\n/**\n * Generate and store embeddings for unembedded chunks via the StorageBackend.\n *\n * The pass is deliberately bounded (see EmbedPassOptions) and resumable: it\n * takes a slice of the backlog, embeds it, and returns so the scheduler can run\n * an index pass before coming back. Draining the whole backlog in one call\n * starves indexing for as long as the call runs.\n *\n * The optional `shouldStop` callback is checked between every batch. When it\n * returns true the embed loop exits early so the caller (e.g. the daemon\n * shutdown handler) can close the pool without racing against active queries.\n *\n * Returns the number of newly embedded chunks.\n */\nexport async function embedChunksWithBackend(\n  backend: StorageBackend,\n  shouldStop?: () => boolean,\n  projectNames?: Map<number, string>,\n  options?: EmbedPassOptions,\n): Promise<number> {\n  const { generateEmbeddings, serializeEmbedding } = await import(\"../embeddings.js\");\n\n  const maxChunks = options?.maxChunks ?? DEFAULT_MAX_CHUNKS_PER_PASS;\n  const maxMillis = options?.maxMillis ?? DEFAULT_MAX_MILLIS_PER_PASS;\n  const deadline = Date.now() + maxMillis;\n\n  const rows = await backend.getUnembeddedChunkIds(undefined, maxChunks);\n  if (rows.length === 0) return 0;\n\n  const total = rows.length;\n  let embedded = 0;\n\n  // Build a summary of what needs embedding: count chunks per project_id\n  const projectChunkCounts = new Map<number, { count: number; samplePath: string }>();\n  for (const row of rows) {\n    const entry = projectChunkCounts.get(row.project_id);\n    if (entry) {\n      entry.count++;\n    } else {\n      projectChunkCounts.set(row.project_id, { count: 1, samplePath: row.path });\n    }\n  }\n  const pName = (pid: number) => projectNames?.get(pid) ?? `project ${pid}`;\n  const projectSummary = Array.from(projectChunkCounts.entries())\n    .map(([pid, { count, samplePath }]) => `  ${pName(pid)}: ${count} chunks (e.g. ${samplePath})`)\n    .join(\"\\n\");\n  process.stderr.write(\n    `[pai-daemon] Embed pass: ${total} unembedded chunks across ${projectChunkCounts.size} project(s)\\n${projectSummary}\\n`\n  );\n\n  // Track current project for transition logging\n  let currentProjectId = -1;\n  let projectEmbedded = 0;\n\n  for (let i = 0; i < rows.length; i += EMBED_BATCH_SIZE) {\n    // Check cancellation between every batch before touching the pool again\n    if (shouldStop?.()) {\n      process.stderr.write(\n        `[pai-daemon] Embed pass cancelled after ${embedded}/${total} chunks (shutdown requested)\\n`\n      );\n      break;\n    }\n\n    // Yield the daemon back to the indexer rather than run past the deadline.\n    // The remaining rows are simply picked up by the next scheduled pass.\n    if (Date.now() >= deadline) {\n      process.stderr.write(\n        `[pai-daemon] Embed pass yielding after ${embedded}/${total} chunks (${maxMillis}ms budget spent); resuming next pass\\n`\n      );\n      break;\n    }\n\n    const batch = rows.slice(i, i + EMBED_BATCH_SIZE);\n\n    // Keep IPC responsive: the forward pass below is synchronous inside the\n    // model, so yield before entering it rather than between chunks.\n    await yieldToEventLoop();\n\n    const vecs = await generateEmbeddings(batch.map((r) => r.text));\n    // Issue the writes together rather than one round-trip at a time. Measured\n    // on this machine the model embeds 40-67 chunks/s in isolation while the\n    // daemon managed ~5/s: the difference was one sequential UPDATE per chunk,\n    // so the pass spent most of its budget waiting on the network, not working.\n    // Concurrency is bounded by the batch size, which the pool handles; the\n    // SQLite backend is synchronous and simply ignores the difference.\n    await Promise.all(\n      batch.map((row, j) => backend.updateEmbedding(row.id, serializeEmbedding(vecs[j])))\n    );\n\n    // Attribute the batch to projects only after it is durably stored, and walk\n    // it in order so a batch straddling a project boundary credits each side\n    // correctly. Rows arrive ordered by project, so a boundary is a real\n    // transition, not the per-chunk flapping this logging used to produce.\n    for (const { project_id, path } of batch) {\n      if (project_id !== currentProjectId) {\n        if (currentProjectId !== -1) {\n          process.stderr.write(\n            `[pai-daemon] Finished ${pName(currentProjectId)}: ${projectEmbedded} chunks embedded\\n`\n          );\n        }\n        const info = projectChunkCounts.get(project_id);\n        process.stderr.write(\n          `[pai-daemon] Embedding ${pName(project_id)} (${info?.count ?? \"?\"} chunks, starting at ${path})\\n`\n        );\n        currentProjectId = project_id;\n        projectEmbedded = 0;\n      }\n      projectEmbedded++;\n    }\n\n    embedded += batch.length;\n\n    // Log progress with current file path for context\n    const lastChunk = batch[batch.length - 1];\n    process.stderr.write(\n      `[pai-daemon] Embedded ${embedded}/${total} chunks (${pName(lastChunk.project_id)}: ${lastChunk.path})\\n`\n    );\n  }\n\n  // Log final project completion\n  if (currentProjectId !== -1) {\n    process.stderr.write(\n      `[pai-daemon] Finished ${pName(currentProjectId)}: ${projectEmbedded} chunks embedded\\n`\n    );\n  }\n\n  return embedded;\n}\n\n// ---------------------------------------------------------------------------\n// Global indexing via StorageBackend\n// ---------------------------------------------------------------------------\n\nexport async function indexAllWithBackend(\n  backend: StorageBackend,\n  registryDb: Database,\n): Promise<{ projects: number; result: IndexResult }> {\n  const projects = registryDb\n    .prepare(\"SELECT id, root_path, claude_notes_dir FROM projects WHERE status = 'active'\")\n    .all() as Array<{ id: number; root_path: string; claude_notes_dir: string | null }>;\n\n  const totals: IndexResult = { filesProcessed: 0, chunksCreated: 0, filesSkipped: 0 };\n\n  for (const project of projects) {\n    await yieldToEventLoop();\n    const r = await indexProjectWithBackend(backend, project.id, project.root_path, project.claude_notes_dir);\n    totals.filesProcessed += r.filesProcessed;\n    totals.chunksCreated += r.chunksCreated;\n    totals.filesSkipped += r.filesSkipped;\n  }\n\n  return { projects: projects.length, result: totals };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuCA,eAAsB,qBACpB,SACA,WACA,UACA,cACA,QACA,MACkB;CAClB,MAAM,UAAU,KAAK,UAAU,aAAa;CAE5C,IAAI;CACJ,IAAI;AACJ,KAAI;AACF,YAAU,aAAa,SAAS,OAAO;AACvC,SAAO,SAAS,QAAQ;SAClB;AACN,SAAO;;CAGT,MAAM,OAAO,WAAW,QAAQ;CAChC,MAAM,QAAQ,KAAK,MAAM,KAAK,QAAQ;CACtC,MAAM,OAAO,KAAK;AAIlB,KADqB,MAAM,QAAQ,YAAY,WAAW,aAAa,KAClD,KAAM,QAAO;AAGlC,OAAM,QAAQ,oBAAoB,WAAW,aAAa;CAG1D,MAAM,YAAY,cAAc,QAAQ;CACxC,MAAM,YAAY,KAAK,KAAK;CAE5B,MAAM,SAAqB,UAAU,KAAK,GAAG,OAAO;EAClD,IAAI,QAAQ,WAAW,cAAc,GAAG,EAAE,WAAW,EAAE,QAAQ;EAC/D;EACA;EACA;EACA,MAAM;EACN,WAAW,EAAE;EACb,SAAS,EAAE;EACX,MAAM,EAAE;EACR,MAAM,EAAE;EACR;EACA,WAAW;EACZ,EAAE;AAGH,OAAM,QAAQ,aAAa,OAAO;AAClC,OAAM,QAAQ,WAAW;EAAE;EAAW,MAAM;EAAc;EAAQ;EAAM;EAAM;EAAO;EAAM,CAAC;AAE5F,QAAO;;AAOT,eAAsB,wBACpB,SACA,WACA,UACA,gBACsB;CACtB,MAAM,SAAsB;EAAE,gBAAgB;EAAG,eAAe;EAAG,cAAc;EAAG;CAEpF,MAAM,eAA2F,EAAE;CAEnG,MAAM,eAAe,KAAK,UAAU,YAAY;AAChD,KAAI,WAAW,aAAa,CAC1B,cAAa,KAAK;EAAE,SAAS;EAAc,UAAU;EAAU,QAAQ;EAAU,MAAM;EAAa,CAAC;CAGvG,MAAM,YAAY,KAAK,UAAU,SAAS;AAC1C,MAAK,MAAM,WAAW,YAAY,UAAU,EAAE;EAE5C,MAAM,OAAO,WADG,SAAS,UAAU,QAAQ,CACX;AAChC,eAAa,KAAK;GAAE;GAAS,UAAU;GAAU,QAAQ;GAAU;GAAM,CAAC;;CAG5E,MAAM,WAAW,KAAK,UAAU,QAAQ;AACxC,MAAK,MAAM,WAAW,YAAY,SAAS,CACzC,cAAa,KAAK;EAAE;EAAS,UAAU;EAAU,QAAQ;EAAS,MAAM;EAAW,CAAC;CAItF;EACE,MAAM,YAAY,KAAK,KAAK;AAC5B,OAAK,MAAM,WAAW,YAAY,SAAS,EAAE;GAE3C,MAAM,OAAO,uBADI,SAAS,QAAQ,CACW;AAC7C,OAAI,CAAC,KAAM;GAEX,MAAM,gBAAgB,GADN,SAAS,UAAU,QAAQ,CACV;GAGjC,MAAM,aAAuB;IAC3B,IAHS,QAAQ,WAAW,eAAe,GAAG,GAAG,EAAE;IAG/C;IAAW,QAAQ;IAAS,MAAM;IACtC,MAAM;IAAe,WAAW;IAAG,SAAS;IAC5C,MAJW,WAAW,KAAK;IAIrB;IAAM;IAAW,WAAW;IACnC;AACD,OAAI;AACF,UAAM,QAAQ,aAAa,CAAC,WAAW,CAAC;WAClC;;;AAMZ,KAAI,CAAC,6BAA6B,SAAS,CACzC,MAAK,MAAM,WAAW,iBAAiB,SAAS,CAC9C,cAAa,KAAK;EAAE;EAAS,UAAU;EAAU,QAAQ;EAAW,MAAM;EAAS,CAAC;AAIxF,KAAI,kBAAkB,mBAAmB,UAAU;AACjD,OAAK,MAAM,WAAW,YAAY,eAAe,CAC/C,cAAa,KAAK;GAAE;GAAS,UAAU;GAAgB,QAAQ;GAAS,MAAM;GAAW,CAAC;EAI5F;GACE,MAAM,YAAY,KAAK,KAAK;AAC5B,QAAK,MAAM,WAAW,YAAY,eAAe,EAAE;IAEjD,MAAM,OAAO,uBADI,SAAS,QAAQ,CACW;AAC7C,QAAI,CAAC,KAAM;IAEX,MAAM,gBAAgB,GADN,SAAS,gBAAgB,QAAQ,CAChB;IAGjC,MAAM,aAAuB;KAC3B,IAHS,QAAQ,WAAW,eAAe,GAAG,GAAG,EAAE;KAG/C;KAAW,QAAQ;KAAS,MAAM;KACtC,MAAM;KAAe,WAAW;KAAG,SAAS;KAC5C,MAJW,WAAW,KAAK;KAIrB;KAAM;KAAW,WAAW;KACnC;AACD,QAAI;AACF,WAAM,QAAQ,aAAa,CAAC,WAAW,CAAC;YAClC;;;AAMZ,MAAI,eAAe,SAAS,SAAS,EAAE;GACrC,MAAM,mBAAmB,eAAe,MAAM,GAAG,GAAiB;GAClE,MAAM,iBAAiB,KAAK,kBAAkB,YAAY;AAC1D,OAAI,WAAW,eAAe,CAC5B,cAAa,KAAK;IAAE,SAAS;IAAgB,UAAU;IAAkB,QAAQ;IAAU,MAAM;IAAa,CAAC;GAEjH,MAAM,kBAAkB,KAAK,kBAAkB,SAAS;AACxD,QAAK,MAAM,WAAW,YAAY,gBAAgB,EAAE;IAElD,MAAM,OAAO,WADG,SAAS,kBAAkB,QAAQ,CACnB;AAChC,iBAAa,KAAK;KAAE;KAAS,UAAU;KAAkB,QAAQ;KAAU;KAAM,CAAC;;;;AAKxF,OAAM,kBAAkB;CAExB,IAAI,kBAAkB;AAEtB,MAAK,MAAM,EAAE,SAAS,UAAU,QAAQ,UAAU,cAAc;AAC9D,MAAI,mBAAmB,mBAAmB;AACxC,SAAM,kBAAkB;AACxB,qBAAkB;;AAEpB;EAEA,MAAM,UAAU,SAAS,UAAU,QAAQ;AAC3C,MAAI;AAGF,OAFgB,MAAM,qBAAqB,SAAS,WAAW,UAAU,SAAS,QAAQ,KAAK,EAElF;IACX,MAAM,MAAM,MAAM,QAAQ,YAAY,WAAW,QAAQ;AACzD,WAAO;AACP,WAAO,iBAAiB,IAAI;SAE5B,QAAO;UAEH;AAEN,UAAO;;;CAKX,MAAM,4BAAY,IAAI,KAAa;AACnC,MAAK,MAAM,EAAE,SAAS,cAAc,aAClC,WAAU,IAAI,SAAS,UAAU,QAAQ,CAAC;CAG5C,MAAM,eAAe,MAAM,QAAQ,sBAAsB,UAAU;CAEnE,MAAM,aAAuB,EAAE;AAC/B,MAAK,MAAM,KAAK,cAAc;EAC5B,MAAM,WAAW,EAAE,SAAS,UAAU,GAAG,EAAE,MAAM,GAAG,GAAkB,GAAG;AACzE,MAAI,CAAC,UAAU,IAAI,SAAS,CAC1B,YAAW,KAAK,EAAE;;AAItB,KAAI,WAAW,SAAS,EACtB,OAAM,QAAQ,YAAY,WAAW,WAAW;AAGlD,QAAO;;AAOT,MAAM,mBAAmB;;AAIzB,MAAM,8BAA8B;;AAEpC,MAAM,8BAA8B;;;;;;;;;;;;;;;AA8BpC,eAAsB,uBACpB,SACA,YACA,cACA,SACiB;CACjB,MAAM,EAAE,oBAAoB,uBAAuB,MAAM,OAAO;CAEhE,MAAM,YAAY,SAAS,aAAa;CACxC,MAAM,YAAY,SAAS,aAAa;CACxC,MAAM,WAAW,KAAK,KAAK,GAAG;CAE9B,MAAM,OAAO,MAAM,QAAQ,sBAAsB,QAAW,UAAU;AACtE,KAAI,KAAK,WAAW,EAAG,QAAO;CAE9B,MAAM,QAAQ,KAAK;CACnB,IAAI,WAAW;CAGf,MAAM,qCAAqB,IAAI,KAAoD;AACnF,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,mBAAmB,IAAI,IAAI,WAAW;AACpD,MAAI,MACF,OAAM;MAEN,oBAAmB,IAAI,IAAI,YAAY;GAAE,OAAO;GAAG,YAAY,IAAI;GAAM,CAAC;;CAG9E,MAAM,SAAS,QAAgB,cAAc,IAAI,IAAI,IAAI,WAAW;CACpE,MAAM,iBAAiB,MAAM,KAAK,mBAAmB,SAAS,CAAC,CAC5D,KAAK,CAAC,KAAK,EAAE,OAAO,kBAAkB,KAAK,MAAM,IAAI,CAAC,IAAI,MAAM,gBAAgB,WAAW,GAAG,CAC9F,KAAK,KAAK;AACb,SAAQ,OAAO,MACb,4BAA4B,MAAM,4BAA4B,mBAAmB,KAAK,eAAe,eAAe,IACrH;CAGD,IAAI,mBAAmB;CACvB,IAAI,kBAAkB;AAEtB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,kBAAkB;AAEtD,MAAI,cAAc,EAAE;AAClB,WAAQ,OAAO,MACb,2CAA2C,SAAS,GAAG,MAAM,gCAC9D;AACD;;AAKF,MAAI,KAAK,KAAK,IAAI,UAAU;AAC1B,WAAQ,OAAO,MACb,0CAA0C,SAAS,GAAG,MAAM,WAAW,UAAU,wCAClF;AACD;;EAGF,MAAM,QAAQ,KAAK,MAAM,GAAG,IAAI,iBAAiB;AAIjD,QAAM,kBAAkB;EAExB,MAAM,OAAO,MAAM,mBAAmB,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC;AAO/D,QAAM,QAAQ,IACZ,MAAM,KAAK,KAAK,MAAM,QAAQ,gBAAgB,IAAI,IAAI,mBAAmB,KAAK,GAAG,CAAC,CAAC,CACpF;AAMD,OAAK,MAAM,EAAE,YAAY,UAAU,OAAO;AACxC,OAAI,eAAe,kBAAkB;AACnC,QAAI,qBAAqB,GACvB,SAAQ,OAAO,MACb,yBAAyB,MAAM,iBAAiB,CAAC,IAAI,gBAAgB,oBACtE;IAEH,MAAM,OAAO,mBAAmB,IAAI,WAAW;AAC/C,YAAQ,OAAO,MACb,0BAA0B,MAAM,WAAW,CAAC,IAAI,MAAM,SAAS,IAAI,uBAAuB,KAAK,KAChG;AACD,uBAAmB;AACnB,sBAAkB;;AAEpB;;AAGF,cAAY,MAAM;EAGlB,MAAM,YAAY,MAAM,MAAM,SAAS;AACvC,UAAQ,OAAO,MACb,yBAAyB,SAAS,GAAG,MAAM,WAAW,MAAM,UAAU,WAAW,CAAC,IAAI,UAAU,KAAK,KACtG;;AAIH,KAAI,qBAAqB,GACvB,SAAQ,OAAO,MACb,yBAAyB,MAAM,iBAAiB,CAAC,IAAI,gBAAgB,oBACtE;AAGH,QAAO;;AAOT,eAAsB,oBACpB,SACA,YACoD;CACpD,MAAM,WAAW,WACd,QAAQ,+EAA+E,CACvF,KAAK;CAER,MAAM,SAAsB;EAAE,gBAAgB;EAAG,eAAe;EAAG,cAAc;EAAG;AAEpF,MAAK,MAAM,WAAW,UAAU;AAC9B,QAAM,kBAAkB;EACxB,MAAM,IAAI,MAAM,wBAAwB,SAAS,QAAQ,IAAI,QAAQ,WAAW,QAAQ,iBAAiB;AACzG,SAAO,kBAAkB,EAAE;AAC3B,SAAO,iBAAiB,EAAE;AAC1B,SAAO,gBAAgB,EAAE;;AAG3B,QAAO;EAAE,UAAU,SAAS;EAAQ,QAAQ;EAAQ"}