/**
 * Modular Syntax Reference for Workflow Agent
 *
 * Contains workflow syntax broken into categories for on-demand retrieval.
 * This replaces embedding WORKFLOW_SYNTAX_COMPACT in system prompt.
 */

/**
 * Syntax modules organized by category
 */
export const SYNTAX_MODULES: Record<string, string> = {
  start: `## Start Step (stepType: 'start')

Config: { inputSchema?: { properties: { [name]: PropertySchema }, required?: string[] } }
NextSteps: { success: 'next_step_slug' }

The start step defines the workflow entry point and optionally declares an input schema.
The input schema follows JSON Schema conventions (type, description, properties, required, items).
Trigger sources (schedules, webhooks, API keys, events) are configured separately — not in step config.

**PropertySchema:** { type, description?, items?, properties?, required? }
Supported types: string, number, integer, boolean, array, object

For array types, use \`items\` to describe the element schema.
For object types, use \`properties\` and \`required\` to describe fields.

**Simple Input Schema:**
\`\`\`json
{
  "inputSchema": {
    "properties": {
      "customerId": { "type": "string", "description": "Customer to process" },
      "priority": { "type": "number", "description": "Processing priority" }
    },
    "required": ["customerId"]
  }
}
\`\`\`

**Rich Input Schema (with nested types):**
\`\`\`json
{
  "inputSchema": {
    "properties": {
      "files": {
        "type": "array",
        "description": "Input files to process",
        "items": {
          "type": "object",
          "properties": {
            "fileId": { "type": "string", "description": "Convex storage ID" },
            "fileName": { "type": "string", "description": "Original file name" }
          },
          "required": ["fileId", "fileName"]
        }
      },
      "options": {
        "type": "object",
        "description": "Processing options",
        "properties": {
          "language": { "type": "string", "description": "Output language" },
          "verbose": { "type": "boolean", "description": "Enable verbose output" }
        }
      }
    },
    "required": ["files"]
  }
}
\`\`\`

**Minimal Start (no input schema):**
\`\`\`json
{}
\`\`\`

**Full Start Step (showing nextSteps):**
\`\`\`json
{
  "stepSlug": "start",
  "name": "Start",
  "stepType": "start",
  "config": {},
  "nextSteps": { "success": "next_step_slug" }
}
\`\`\`
Note: The port name MUST be "success" (not "next" or "default").`,

  llm: `## LLM Step (stepType: 'llm')

Config: { name (REQUIRED), systemPrompt (REQUIRED), userPrompt?, tools?: string[], knowledgeFileIds?, outputFormat?: 'text'|'json', outputSchema?, contextVariables? }
NextSteps: { success: 'next_step', error?: 'error_handler' }

**RESTRICTION:** tools and outputFormat: 'json' CANNOT be combined in a single step. If you need both, split into two steps: one text+tools step (to gather data), then a separate JSON step without tools (to format the result).

**CRITICAL FIELDS:**
- name: REQUIRED - human-readable name
- systemPrompt: REQUIRED - role and instructions (NOT "prompt")
- knowledgeFileIds: Optional array of file IDs (or template expression resolving to an array) to scope rag_search.
  When set, rag_search automatically searches only these files without relying on the LLM to pass fileIds.
  Example: "knowledgeFileIds": "{{input.knowledgeFiles | map('fileId')}}"

**LLM Step Example:**
\`\`\`json
{
  "name": "Analyze Customer",
  "systemPrompt": "You are a customer analyst. Analyze the customer data and provide insights.",
  "userPrompt": "Analyze this customer: {{steps.get_customer.output.data}}",
  "tools": ["customer_read", "product_read"],
  "outputFormat": "json"
}
\`\`\`

**LLM Step with RAG Scoping:**
\`\`\`json
{
  "name": "Search Templates",
  "systemPrompt": "Search the knowledge base for relevant clauses.",
  "tools": ["rag_search"],
  "knowledgeFileIds": "{{input.knowledgeFiles | map('fileId')}}",
  "outputFormat": "text"
}
\`\`\`

**LLM Output:**
- outputFormat: 'text' (default) → output.data is a plain string. Do NOT access sub-fields.
  ✗ {{steps.greet.output.data.result}} — WRONG (string has no sub-fields)
  ✓ {{steps.greet.output.data}} — CORRECT
- outputFormat: 'json' → output.data is the parsed JSON object. Access fields directly.
  ✓ {{steps.analyze.output.data.score}}

**Available Tools for LLM Steps:**
- customer_read: Fetch customer by ID, email, or list all (operation: get_by_id, get_by_email, list)
- product_read: Fetch product by ID or list all (operation: get_by_id, list)
- rag_search: Search knowledge base (automatically scoped when knowledgeFileIds is set)`,

  action: `## Action Step (stepType: 'action')

Config: { type (action type), parameters, retryPolicy? }
NextSteps: { success: 'next_step', error?: 'error_handler' }

**IMPORTANT:** stepType is 'action', the config.type specifies the action type.

**ACTION TYPES:**

### workflow_processing_records
Operations: find_unprocessed, record_processed

**find_unprocessed:**
\`\`\`json
{
  "type": "workflow_processing_records",
  "parameters": {
    "operation": "find_unprocessed",
    "tableName": "customers",
    "backoffHours": 168,
    "filterExpression": "status == \\"active\\""
  }
}
\`\`\`
- backoffHours: minimum hours before reprocessing the same record (e.g., 168 = 7 days)
- filterExpression: JEXL syntax for filtering candidates

**record_processed:**
\`\`\`json
{
  "type": "workflow_processing_records",
  "parameters": {
    "operation": "record_processed",
    "tableName": "customers",
    "recordId": "{{steps.find_customer.output.data._id}}",
    "recordCreationTime": "{{steps.find_customer.output.data._creationTime}}"
  }
}
\`\`\`

### integration_processing_records
Operations: find_unprocessed, record_processed
Incremental, deduplicated processing of EXTERNAL data sources (SQL/REST integrations). Use workflow_processing_records for Convex tables; use this for integration data.

**find_unprocessed** fetches via the integration and atomically claims ONE unprocessed record. Output data is an envelope: { record, recordId, incrementalValue, tableName } or null when everything is processed.

Incremental strategies (incrementalConfig.strategy):
| Strategy | Use case | Resume mechanism |
|----------|----------|------------------|
| timestamp_based | SQL with modified_date, APIs with updated_at | highest processed timestamp injected as fetchParams[resumeParamKey], formatted per timestampFormat ('iso' \\| 'epoch_ms' \\| 'epoch_s' \\| 'date') |
| id_based | monotonically increasing ids | highest processed id injected as fetchParams[resumeParamKey] (e.g. since_id) |
| cursor_based | Shopify-style pagination | opaque cursor read from cursorPath on the fetch result; up to maxPages (default 5) pages per run |
| full_scan | no incremental support | dedupe only — always set fetchParams.limit |

SQL example (timestamp_based):
\`\`\`json
{
  "type": "integration_processing_records",
  "parameters": {
    "operation": "find_unprocessed",
    "dataSource": {
      "integrationName": "protel",
      "fetchOperation": "list_guests",
      "fetchParams": { "status": "active", "limit": 100 },
      "recordIdField": "guest_id",
      "sourceIdentifier": "guests",
      "localFilterExpression": "daysAgo(modified_date) < 30",
      "incrementalConfig": {
        "strategy": "timestamp_based",
        "timestampField": "modified_date",
        "resumeParamKey": "fromDate",
        "timestampFormat": "iso"
      }
    },
    "backoffHours": -1
  }
}
\`\`\`

REST example (cursor_based):
\`\`\`json
{
  "type": "integration_processing_records",
  "parameters": {
    "operation": "find_unprocessed",
    "dataSource": {
      "integrationName": "shopify",
      "fetchOperation": "list_orders",
      "fetchParams": { "status": "any", "limit": 50 },
      "recordIdField": "id",
      "sourceIdentifier": "orders",
      "recordsPath": "result.orders",
      "localFilterExpression": "financial_status == \\"paid\\" && fulfillment_status == null",
      "incrementalConfig": {
        "strategy": "cursor_based",
        "resumeParamKey": "page_info",
        "cursorPath": "result.page_info.next",
        "maxPages": 5
      }
    },
    "backoffHours": 24
  }
}
\`\`\`
- backoffHours: minimum hours before reprocessing the same record (-1 = never reprocess)
- The fetch operation must be read-only (approval-gated operations are rejected)
- localFilterExpression: JEXL post-filter applied to each fetched record
- recordsPath: where the record array lives on the integration result (defaults: "data" for SQL, "result" for REST)

**record_processed** marks the claimed record processed and advances the watermark:
\`\`\`json
{
  "type": "integration_processing_records",
  "parameters": {
    "operation": "record_processed",
    "integrationName": "protel",
    "sourceIdentifier": "guests",
    "recordId": "{{steps.find_guest.output.data.recordId}}",
    "incrementalValue": "{{steps.find_guest.output.data.incrementalValue}}"
  }
}
\`\`\`
The watermark only advances on record_processed (never on claim), so a failed run cannot skip records.

### customer
Operations: create, query, filter, update
\`\`\`json
{
  "type": "customer",
  "parameters": {
    "operation": "query",
    "status": "active",
    "paginationOpts": { "numItems": 10, "cursor": null }
  }
}
\`\`\`

### product
Operations: create, get_by_id, query, filter, update, hydrate_fields

### conversation
Operations: create, query_messages, update, create_from_email

**Email outbound (channel: email):**
\`\`\`json
{
  "type": "conversation",
  "parameters": {
    "operation": "create",
    "customerId": "{{variables.customerId}}",
    "subject": "{{variables.emailSubject}}",
    "channel": "email",
    "direction": "outbound",
    "metadata": {
      "emailSubject": "{{variables.emailSubject}}",
      "emailBody": "{{variables.emailBody}}",
      "emailPreview": "{{variables.emailPreview}}",
      "customerEmail": "{{variables.customerEmail}}"
    }
  }
}
\`\`\`
Required metadata: emailSubject, emailBody, customerEmail
Optional metadata: emailPreview, emailCc, emailBcc
Note: there is no direct "send_email" action. Create a conversation with email metadata, then create an approval to trigger the send.

### approval
Operation: create_approval
\`\`\`json
{
  "type": "approval",
  "parameters": {
    "operation": "create_approval",
    "resourceType": "conversations",
    "resourceId": "{{steps.create_conv.output.data._id}}",
    "priority": "medium",
    "description": "Review before sending"
  }
}
\`\`\`

### set_variables
\`\`\`json
{
  "type": "set_variables",
  "parameters": {
    "variables": [
      { "name": "customerId", "value": "{{steps.find.output.data._id}}" }
    ]
  }
}
\`\`\`

### integration
For external APIs (Shopify, Circuly, etc.)
\`\`\`json
{
  "type": "integration",
  "parameters": {
    "name": "shopify",
    "operation": "getProducts",
    "params": { "limit": 50 }
  }
}
\`\`\`

### document
Operations: create, update, retrieve, list, generate_docx, get_metadata, compare, extract_docx_structured, apply_docx_structured

**create** — Save a file to the documents hub:
\`\`\`json
{
  "type": "document",
  "parameters": {
    "operation": "create",
    "fileId": "{{steps.generate.output.data.fileId}}",
    "title": "Monthly Report",
    "folderPath": "reports/monthly"
  }
}
\`\`\`
- fileId: storage ID of the file to save (required)
- title: document title (optional, defaults to original file name)
- folderPath: slash-separated folder path, created automatically if missing (optional)
- Requires organizationId in workflow variables (auto-injected)

**update** — Update an existing document by file ID:
\`\`\`json
{
  "type": "document",
  "parameters": {
    "operation": "update",
    "fileId": "{{steps.find_doc.output.data.fileId}}",
    "title": "Updated Title",
    "content": "New content"
  }
}
\`\`\`
- fileId: required
- Optional: title, content, mimeType, extension, metadata, sourceProvider

**retrieve** — Fetch document content by file ID:
\`\`\`json
{
  "type": "document",
  "parameters": {
    "operation": "retrieve",
    "fileId": "{{steps.find_doc.output.data.fileId}}"
  }
}
\`\`\`
- Optional: chunkStart, chunkEnd (for partial retrieval), returnChunks (boolean)

**generate_docx** — Generate a DOCX file from markdown or HTML:
\`\`\`json
{
  "type": "document",
  "parameters": {
    "operation": "generate_docx",
    "fileName": "report.docx",
    "sourceType": "markdown",
    "content": "{{steps.draft.output.data}}"
  }
}
\`\`\`
- sourceType: "markdown" or "html"
- Requires organizationId in workflow variables

**get_metadata** — Fetch file metadata for one or more files:
\`\`\`json
{
  "type": "document",
  "parameters": {
    "operation": "get_metadata",
    "fileIds": ["{{steps.file1.output.data.fileId}}", "{{steps.file2.output.data.fileId}}"]
  }
}
\`\`\`

**compare** — Compare two documents and return differences:
\`\`\`json
{
  "type": "document",
  "parameters": {
    "operation": "compare",
    "baseFileId": "{{steps.original.output.data.fileId}}",
    "comparisonFileId": "{{steps.revised.output.data.fileId}}"
  }
}
\`\`\`
- Optional: baseFileName, comparisonFileName, maxChanges

**extract_docx_structured** — Extract structured paragraph data from a DOCX file:
\`\`\`json
{
  "type": "document",
  "parameters": {
    "operation": "extract_docx_structured",
    "fileId": "{{input.baseFileId}}"
  }
}
\`\`\`
- fileId: Convex storage ID of the DOCX file (required)
- Returns: { source_hash, metadata: { paragraph_count, table_count, group_count }, lightweight: [{ key, text, editable, style }], groups: [[{ key, text, editable, style }]] }
- Paragraph keys are stable (e.g., "p_0", "tbl_0_r0_c0_p0") and used in apply_docx_structured
- editable: false for paragraphs with hyperlinks, images, tracked changes, SDT controls
- groups: semantic batches of editable paragraphs, split at heading boundaries (uses outlineLvl). Use groups instead of chunk() for batch processing loops.

**apply_docx_structured** — Apply text modifications to a DOCX file preserving all formatting:
\`\`\`json
{
  "type": "document",
  "parameters": {
    "operation": "apply_docx_structured",
    "templateFileId": "{{input.baseFileId}}",
    "sourceHash": "{{steps.extract.output.data.source_hash}}",
    "modifications": "{{variables.allModifications}}",
    "fileName": "modified-contract",
    "trackChanges": true,
    "author": "AI Assistant"
  }
}
\`\`\`
- templateFileId: Convex storage ID of the original DOCX (required)
- sourceHash: SHA-256 hash from extract step for validation (required)
- modifications: Array of { key: string, text: string } (required)
- fileName: Output file name without extension (required)
- trackChanges: Use Word Track Changes markup (optional, default false)
- author: Author name for Track Changes (optional, default "AI Assistant")
- Returns: { success, fileStorageId, downloadUrl, fileName, size, report }`,

  condition: `## Condition Step (stepType: 'condition')

Config: { expression (JEXL), description?, variables? }
NextSteps: { true: 'if_true_step', false: 'if_false_step' }

**Condition Example:**
\`\`\`json
{
  "expression": "steps.find_customer.output.data != null"
}
\`\`\`

**Common Expressions:**
- Check entity exists: \`steps.find_step.output.data != null\`
- Check array not empty: \`steps.query.output.data|length > 0\`
- Compare values: \`steps.check.output.data.status == "active"\`
- Boolean logic: \`status == "active" && count > 5\``,

  loop: `## Loop Step (stepType: 'loop')

Config: { items, itemVariable?: 'item', indexVariable?: 'index', maxIterations?: 1000, continueOnError? }
NextSteps: { loop: 'loop_body_step', done: 'after_loop_step', error?: 'error_handler' }

**Loop Example:**
\`\`\`json
{
  "items": "{{steps.query_customers.output.data}}",
  "itemVariable": "customer",
  "maxIterations": 100
}
\`\`\`

**Loop Variable Access:**
- Current item: {{loop.item}}
- Current index: {{loop.index}}
- Iteration count: {{loop.state.iterations}}`,

  workflow_config: `## WORKFLOW CONFIG (workflowConfig.config)

The optional \`config\` field on \`workflowConfig\` supports these fields:

### timeout (number, optional)
Workflow timeout in milliseconds. The workflow execution will be aborted if it exceeds this duration.
\`\`\`json
{ "timeout": 120000 }
\`\`\`

### retryPolicy (object, optional)
Default retry policy applied to action steps.
\`\`\`json
{ "retryPolicy": { "maxRetries": 3, "backoffMs": 2000 } }
\`\`\`

### variables (object, optional)
Initial workflow-level variables accessible to all steps via \`{{config.variableName}}\`.
\`organizationId\` is automatically injected — no need to set it.
\`\`\`json
{ "variables": { "backoffHours": 72, "defaultStatus": "active" } }
\`\`\`
Access: \`{{config.backoffHours}}\`, \`{{config.defaultStatus}}\`

### Full Example
\`\`\`json
{
  "workflowConfig": {
    "name": "My Workflow",
    "description": "Example with config",
    "config": {
      "timeout": 120000,
      "retryPolicy": { "maxRetries": 2, "backoffMs": 1000 },
      "variables": {
        "backoffHours": 72,
        "conversationStatus": "open"
      }
    }
  }
}
\`\`\``,

  variables: `## VARIABLE SYNTAX

**All references MUST use a known prefix.** Bare variable names like \`{{myVar}}\` are invalid.

### Known Prefixes
| Prefix | Source | Example |
|--------|--------|---------|
| steps | Step outputs | {{steps.get_customer.output.data._id}} |
| config | Workflow config variables | {{config.backoffHours}} |
| variables | set_variables action output | {{variables.counter}} |
| input | Workflow input parameters | {{input.customerId}} |
| secrets | Encrypted secrets | {{secrets.apiKey}} |
| loop | Loop context | {{loop.item}}, {{loop.index}} |

### Step Output Access
All step outputs are wrapped: steps.{step_slug}.output.data
- Single entity: {{steps.get_customer.output.data._id}}
- First array item: {{steps.query.output.data|first}} (use |first instead of [0] — [0] crashes if data is undefined)
- Array length: {{steps.query.output.data|length}}

### Step Output Data by Type
| Step Type | output.data | Example |
|-----------|-------------|---------|
| llm (text) | Plain string | {{steps.greet.output.data}} |
| llm (json) | Parsed JSON object | {{steps.analyze.output.data.score}} |
| action | Depends on type/operation | {{steps.query.output.data._id}} |
| condition | { passed, description, expression, message } | {{steps.check.output.data.passed}} |
| start | String | {{steps.start.output.data}} |

### Config Variables (from workflowConfig.config.variables)
- {{config.backoffHours}}
- {{config.defaultStatus}}

### User-Defined Variables (from set_variables action)
- {{variables.counter}}
- {{variables.customerId}}

### System Variables (no prefix needed)
- {{organizationId}} - Current organization
- {{executionId}} - Current workflow execution
- {{now}} - Current timestamp (ISO 8601)
- {{nowMs}} - Current timestamp (milliseconds)

### JEXL Filters
- Array: |length, |first, |last, |map("prop"), |filter(), |unique, |flatten, |slice(start, end), |sort("field", "asc"), |reverse, |chunk(size)
- String: |join(", "), |upper, |lower, |trim
- Boolean: |hasOverlap(otherArray)
- Type: |string, |number, |boolean, |parseJSON
- Date: |isoDate, |parseDate, |daysAgo, |hoursAgo, |minutesAgo, |isBefore(date), |isAfter(date)
- Format: |formatList("template", "separator")
- Lookup: |find("field", value), |filterBy("field", value)

**IMPORTANT:** Use |first instead of [0] when the array might be undefined (e.g., branching paths where only one step runs). [0] will throw an error on undefined arrays, while |first safely returns undefined.

**COMMON MISTAKES:**
- ✗ \`{{step_slug.result}}\` → ✓ \`{{steps.step_slug.output.data}}\`
- ✗ \`{{myVar}}\` → ✓ \`{{config.myVar}}\` or \`{{variables.myVar}}\`
- ✗ \`{{step.xxx}}\` → ✓ \`{{steps.xxx}}\` (plural "steps")`,

  output: `## Output Step (stepType: 'output')

Config: { mapping?: Record<string, unknown> }
NextSteps: {} (empty — output steps have no outgoing connections)

The output step is optional. It defines what the workflow returns as its final output.
If omitted, the workflow output falls back to sanitized variables (secrets and system fields stripped).
Use mapping to select which variables or step outputs to include. Values support {{...}} template syntax with full type preservation.

**Output with Mapping:**
\`\`\`json
{
  "stepSlug": "finish",
  "name": "Return Results",
  "stepType": "output",

  "config": {
    "mapping": {
      "analysis": "{{steps.analyze.output.data}}",
      "customerId": "{{variables.customerId}}",
      "processedAt": "{{now}}",
      "version": 1
    }
  },
  "nextSteps": {}
}
\`\`\`

**Output with No Mapping (side-effect-only workflow):**
\`\`\`json
{
  "stepSlug": "finish",
  "name": "Done",
  "stepType": "output",

  "config": {},
  "nextSteps": {}
}
\`\`\`

**IMPORTANT:**
- mapping values can be {{...}} template strings (which preserve types) or literal JSON values (numbers, booleans, null, arrays, objects)
- Do NOT reference secrets in mapping (e.g., {{secrets.apiKey}}) — this leaks sensitive data
- If mapping is empty or omitted, the workflow output is null
- nextSteps MUST be empty: {}`,

  hello_world: `## Hello World Example

A minimal 3-step workflow: start → llm → output.
Use this as a starting template when building new workflows.

**workflowConfig:**
\`\`\`json
{
  "name": "Hello World",
  "description": "A simple greeting workflow"
}
\`\`\`

**stepsConfig:**
\`\`\`json
[
  {
    "stepSlug": "start",
    "name": "Start",
    "stepType": "start",
    "config": {},
    "nextSteps": { "success": "greet" }
  },
  {
    "stepSlug": "greet",
    "name": "Generate Greeting",
    "stepType": "llm",
    "config": {
      "name": "Greeter",
      "systemPrompt": "You are a friendly assistant. Greet the user warmly.",
      "outputFormat": "text"
    },
    "nextSteps": { "success": "finish" }
  },
  {
    "stepSlug": "finish",
    "name": "Return Greeting",
    "stepType": "output",
    "config": {
      "mapping": {
        "greeting": "{{steps.greet.output.data}}"
      }
    },
    "nextSteps": {}
  }
]
\`\`\`

**COMMON MISTAKES — DO NOT:**
- ✗ \`{{step_slug.result}}\` → ✓ \`{{steps.step_slug.output.data}}\` (step output access)
- ✗ \`{{myVar}}\` → ✓ \`{{config.myVar}}\` or \`{{variables.myVar}}\` (all refs need a prefix)
- ✗ \`{{step.xxx}}\` → ✓ \`{{steps.xxx}}\` (plural "steps")
- ✗ \`prompt\` → ✓ \`systemPrompt\` (LLM step config field)
- ✗ \`nextSteps: { next: ... }\` or \`{ default: ... }\` → ✓ \`nextSteps: { success: ... }\` (start/llm/action port is always "success")

**VALID nextSteps PORTS BY STEP TYPE:**
- start, llm, action: \`{ "success": "next_step_slug" }\`
- condition: \`{ "true": "step_if_true", "false": "step_if_false" }\`
- loop: \`{ "loop": "loop_body_step", "done": "after_loop_step" }\`
- output: \`{}\` (no outgoing connections)`,
};

/**
 * Get syntax reference by category
 */
export function getSyntaxReference(opts: { category: string }): {
  operation: 'get_syntax_reference';
  category: string;
  found: boolean;
  syntax: string;
} {
  const category = opts.category;
  const syntax = SYNTAX_MODULES[category];

  if (!syntax) {
    return {
      operation: 'get_syntax_reference',
      category,
      found: false,
      syntax: `Unknown category: ${category}. Available categories: ${Object.keys(SYNTAX_MODULES).join(', ')}`,
    };
  }

  return {
    operation: 'get_syntax_reference',
    category,
    found: true,
    syntax,
  };
}

/**
 * Get all syntax modules combined
 */
export function getAllSyntax(): {
  found: boolean;
  syntax: string;
} {
  const combined = Object.values(SYNTAX_MODULES).join('\n\n---\n\n');
  return { found: true, syntax: combined };
}

// Category descriptions - kept in sync with SYNTAX_MODULES keys
const SYNTAX_CATEGORY_DESCRIPTIONS: Record<string, string> = {
  start:
    'Start step configuration (workflow entry point with optional inputSchema)',
  llm: 'LLM step configuration (AI agent with tools)',
  action:
    'Action step types and parameters (workflow_processing_records, integration_processing_records, customer, conversation, approval, set_variables, integration, document)',
  condition: 'Condition step with JEXL expressions',
  loop: 'Loop step for iteration',
  output: 'Output step configuration (workflow output via outputMapping)',
  workflow_config:
    'Workflow-level config: timeout, retryPolicy, and initial variables',
  variables: 'Variable syntax and JEXL filters',
  hello_world:
    'Complete hello world example: minimal 3-step workflow (start → llm → output)',
};

/**
 * List available syntax categories
 * Dynamically derived from SYNTAX_MODULES to prevent drift
 */
export function listSyntaxCategories(): {
  operation: 'list_syntax_categories';
  categories: { key: string; description: string }[];
} {
  return {
    operation: 'list_syntax_categories',
    categories: Object.keys(SYNTAX_MODULES).map((key) => ({
      key,
      description:
        SYNTAX_CATEGORY_DESCRIPTIONS[key] ?? 'No description available',
    })),
  };
}
