import type { Block, Program, Value } from '@promptscript/core';
import { BaseFormatter } from '../base-formatter.js';
import { GlobCategorizer } from '../extractors/glob-categorizer.js';
import type { FormatOptions, FormatterOutput } from '../types.js';

/**
 * Supported Cursor format versions.
 *
 * - `modern`: MDC format with YAML frontmatter (`.cursor/rules/project.mdc`)
 * - `multifile`: Multiple MDC files with glob-based targeting
 * - `legacy`: Plain text (`.cursorrules`) — deprecated
 * - `agents-md`: Plain markdown at `AGENTS.md` (Cursor 2.4+)
 *
 * @note The `frontmatter` alias has been removed from this type. It is still
 * accepted by the formatter as a silent fallback to `modern` for backward
 * compatibility, but it no longer appears in autocomplete.
 */
export type CursorVersion = 'modern' | 'legacy' | 'multifile' | 'agents-md';

/**
 * Cursor formatter version information.
 */
export const CURSOR_VERSIONS = {
  modern: {
    name: 'modern',
    description:
      'MDC format with YAML frontmatter (.cursor/rules/project.mdc) + slash commands (.cursor/commands/)',
    outputPath: '.cursor/rules/project.mdc',
    cursorVersion: '0.45+',
    introduced: '2024-12',
  },
  'agents-md': {
    name: 'agents-md',
    description: 'Plain markdown at AGENTS.md (Cursor 2.4+) — no frontmatter required',
    outputPath: 'AGENTS.md',
    cursorVersion: '2.4+',
    introduced: '2025-06',
  },
  multifile: {
    name: 'multifile',
    description: 'Multiple MDC files with glob-based targeting + slash commands',
    outputPath: '.cursor/rules/project.mdc',
    cursorVersion: '0.45+',
    introduced: '2024-12',
  },
  legacy: {
    name: 'legacy',
    description: 'Plain text format (.cursorrules) - DEPRECATED',
    outputPath: '.cursorrules',
    cursorVersion: '< 0.45',
    deprecated: true,
    deprecatedSince: '2024-12',
  },
} as const;

/**
 * Configuration for glob-based rule files.
 */
interface GlobConfig {
  /** Name used for the output file */
  name: string;
  /** Glob patterns to match */
  patterns: string[];
  /** Custom content for named entries (overrides auto-generated content) */
  content?: string;
  /** Description for frontmatter */
  description: string;
}

/**
 * Formatter for Cursor rules and commands.
 *
 * Supports four versions:
 * - **modern** (default): `.cursor/rules/project.mdc` with YAML frontmatter
 *   + `.cursor/commands/*.md` for multi-line shortcuts
 * - **multifile**: Multiple MDC files with glob-based targeting + commands
 * - **legacy**: `.cursorrules` plain text (deprecated, no commands)
 * - **agents-md**: Plain markdown at `AGENTS.md` (Cursor 2.4+) — no frontmatter
 *
 * ## Slash Commands (Cursor 1.6+)
 *
 * Multi-line `@shortcuts` are automatically converted to executable
 * slash commands in `.cursor/commands/`:
 *
 * ```promptscript
 * @shortcuts {
 *   // Single-line → stays in rules as documentation
 *   "/review": "Review code quality"
 *
 *   // Multi-line → becomes .cursor/commands/test.md
 *   "/test": \"""
 *     Write unit tests using:
 *     - Vitest as the test runner
 *     - AAA pattern
 *   \"""
 * }
 * ```
 *
 * @example
 * ```yaml
 * # Modern format (default)
 * targets:
 *   - cursor
 *
 * # Legacy format for older Cursor versions
 * targets:
 *   - cursor:
 *       version: legacy
 * ```
 *
 * @see https://cursor.com/docs/context/rules
 * @see https://cursor.com/changelog/1-6
 */
export class CursorFormatter extends BaseFormatter {
  readonly name = 'cursor';
  readonly outputPath = CURSOR_VERSIONS.modern.outputPath;
  readonly description = 'Cursor rules (MDC with frontmatter)';
  readonly defaultConvention = 'markdown';

  /**
   * Get supported versions for this formatter.
   */
  static getSupportedVersions(): typeof CURSOR_VERSIONS {
    return CURSOR_VERSIONS;
  }

  override referencesMode(): 'directory' | 'inline' | 'none' {
    return 'inline';
  }

  format(ast: Program, options?: FormatOptions): FormatterOutput {
    // Validate convention - Cursor only supports markdown
    const convention = options?.convention ?? this.defaultConvention;
    if (convention !== 'markdown') {
      const conventionName = typeof convention === 'string' ? convention : 'custom';
      throw new Error(
        `Cursor formatter does not support '${conventionName}' convention. ` +
          `Only 'markdown' convention is supported for Cursor targets.`
      );
    }

    const version = this.resolveVersion(options?.version);

    if (version === 'legacy') {
      return this.formatLegacy(ast, options);
    }

    if (version === 'multifile') {
      return this.formatMultifile(ast, options);
    }

    if (version === 'agents-md') {
      return this.formatAgentsMd(ast, options);
    }

    return this.formatModern(ast, options);
  }

  /**
   * Resolve version string to CursorVersion.
   *
   * The `'frontmatter'` string is accepted as a silent alias for `'modern'`
   * for backward compatibility. It no longer appears in the exported
   * `CursorVersion` type or `CURSOR_VERSIONS` map.
   */
  private resolveVersion(version?: string): CursorVersion {
    if (version === 'legacy') {
      return 'legacy';
    }
    if (version === 'multifile') {
      return 'multifile';
    }
    if (version === 'agents-md') {
      return 'agents-md';
    }
    // 'frontmatter' is kept as a silent backward-compat alias for 'modern'
    if (version === 'frontmatter') {
      return 'modern';
    }
    return 'modern';
  }

  /**
   * Generate inline references section from @skills block.
   * Finds all skills with resources in a references/ subdirectory and
   * appends them as a ## References section.
   */
  private inlineSkillReferences(ast: Program): string {
    const skillsBlock = this.findBlock(ast, 'skills');
    if (!skillsBlock) return '';

    const props = this.getProps(skillsBlock.content);
    const sections: string[] = [];

    for (const [skillName, value] of Object.entries(props)) {
      if (!value || typeof value !== 'object' || Array.isArray(value)) continue;
      const obj = value as Record<string, Value>;
      const resources = obj['resources'];
      if (!resources || !Array.isArray(resources)) continue;

      for (const r of resources) {
        if (!r || typeof r !== 'object' || Array.isArray(r)) continue;
        const res = r as Record<string, Value>;
        const relativePath = res['relativePath'] ? this.valueToString(res['relativePath']) : '';
        const content = res['content'] ? this.valueToString(res['content']) : '';
        if (!relativePath.includes('references/')) continue;

        const fileName = relativePath.split('/').pop() ?? relativePath;
        sections.push(`### ${fileName} (from ${skillName})\n\n${content}`);
      }
    }

    if (sections.length === 0) return '';
    return '\n\n## References\n\n' + sections.join('\n\n---\n\n');
  }

  /**
   * Format for modern Cursor (0.45+) with MDC frontmatter.
   */
  private formatModern(ast: Program, options?: FormatOptions): FormatterOutput {
    const sections: string[] = [];

    // Add YAML frontmatter
    const frontmatter = this.frontmatter(ast);
    sections.push(frontmatter);

    this.addCommonSections(ast, sections);

    // Generate command files for multi-line shortcuts
    const commandFiles = this.generateCommandFiles(ast);

    const inlineRefs = this.inlineSkillReferences(ast);

    return {
      path: options?.outputPath ?? CURSOR_VERSIONS.modern.outputPath,
      content: sections.join('\n\n') + '\n' + inlineRefs,
      additionalFiles: commandFiles.length > 0 ? commandFiles : undefined,
    };
  }

  /**
   * Format for legacy Cursor (< 0.45) with plain .cursorrules.
   * @deprecated Use modern format for Cursor 0.45+
   */
  private formatLegacy(ast: Program, options?: FormatOptions): FormatterOutput {
    const sections: string[] = [];

    // No frontmatter for legacy format
    this.addCommonSections(ast, sections);

    const inlineRefs = this.inlineSkillReferences(ast);

    return {
      path: options?.outputPath ?? CURSOR_VERSIONS.legacy.outputPath,
      content: sections.join('\n\n') + '\n' + inlineRefs,
    };
  }

  /**
   * Format for Cursor 2.4+ `AGENTS.md` plain markdown output.
   *
   * `AGENTS.md` at the project root is a first-class, frontmatter-free
   * alternative to `.cursor/rules/`. Cursor reads it automatically when the
   * project is opened. No slash commands are generated for this version —
   * use `modern` if slash commands are required.
   *
   * @see https://cursor.com/changelog/2-4
   */
  private formatAgentsMd(ast: Program, options?: FormatOptions): FormatterOutput {
    const sections: string[] = [];

    // No frontmatter — plain markdown only
    this.addCommonSections(ast, sections);

    const inlineRefs = this.inlineSkillReferences(ast);

    return {
      path: options?.outputPath ?? CURSOR_VERSIONS['agents-md'].outputPath,
      content: sections.join('\n\n') + '\n' + inlineRefs,
    };
  }

  /**
   * Format for multifile Cursor with glob-based targeting.
   * Generates separate .mdc files for different glob patterns.
   */
  private formatMultifile(ast: Program, options?: FormatOptions): FormatterOutput {
    const additionalFiles: FormatterOutput[] = [];

    // Extract globs from @guards block
    const globs = this.extractGlobs(ast);

    // Generate glob-specific rule files
    for (const globConfig of globs) {
      const globFile = this.generateGlobFile(ast, globConfig);
      if (globFile) {
        additionalFiles.push(globFile);
      }
    }

    // Generate command files for multi-line shortcuts
    const commandFiles = this.generateCommandFiles(ast);
    additionalFiles.push(...commandFiles);

    // Main file with alwaysApply: true for core rules
    const mainSections: string[] = [];
    mainSections.push(this.frontmatter(ast));
    mainSections.push(this.intro(ast));

    const techStack = this.techStack(ast);
    if (techStack) mainSections.push(techStack);

    const architecture = this.architecture(ast);
    if (architecture) mainSections.push(architecture);

    const codeStyle = this.codeStyle(ast);
    if (codeStyle) mainSections.push(codeStyle);

    const gitCommits = this.gitCommits(ast);
    if (gitCommits) mainSections.push(gitCommits);

    const configFiles = this.configFiles(ast);
    if (configFiles) mainSections.push(configFiles);

    const postWork = this.postWork(ast);
    if (postWork) mainSections.push(postWork);

    const documentation = this.documentation(ast);
    if (documentation) mainSections.push(documentation);

    const diagrams = this.diagrams(ast);
    if (diagrams) mainSections.push(diagrams);

    const examples = this.examples(ast);
    if (examples) mainSections.push(examples);

    const never = this.never(ast);
    if (never) mainSections.push(never);

    const inlineRefs = this.inlineSkillReferences(ast);

    return {
      path: options?.outputPath ?? CURSOR_VERSIONS.modern.outputPath,
      content: mainSections.join('\n\n') + '\n' + inlineRefs,
      additionalFiles: additionalFiles.length > 0 ? additionalFiles : undefined,
    };
  }

  /**
   * Extract glob patterns from @guards block.
   *
   * Glob patterns are categorized using the shared {@link GlobCategorizer}
   * which matches globs to `@standards` entries via extension/keyword hints.
   * Only globs that match an existing `@standards` key produce output files.
   */
  private extractGlobs(ast: Program): GlobConfig[] {
    const guards = this.findBlock(ast, 'guards');
    if (!guards) return [];

    const globs: GlobConfig[] = [];
    const props = this.getProps(guards.content);

    // Handle globs property (array of patterns) via GlobCategorizer
    const globPatterns = props['globs'];
    if (Array.isArray(globPatterns)) {
      const standards = this.findBlock(ast, 'standards');
      const categorizer = new GlobCategorizer(this.standardsExtractor);
      const categorized = categorizer.categorize(
        globPatterns.filter((p): p is string => typeof p === 'string'),
        standards?.content ?? null
      );

      for (const cat of categorized) {
        globs.push({
          name: cat.name,
          patterns: cat.patterns,
          content: cat.content,
          description: cat.description,
        });
      }
    }

    // Handle named entries with applyTo patterns (unchanged)
    for (const [key, value] of Object.entries(props)) {
      if (key === 'globs') continue;
      if (!this.isSafeSkillName(key)) continue;
      if (!value || typeof value !== 'object' || Array.isArray(value)) continue;

      const obj = value as Record<string, Value>;
      const applyTo = obj['applyTo'];
      if (!applyTo || !Array.isArray(applyTo)) continue;

      globs.push({
        name: key,
        patterns: applyTo.map((p) => this.valueToString(p)),
        content: obj['content'] ? this.valueToString(obj['content']) : undefined,
        description: obj['description'] ? this.valueToString(obj['description']) : `${key} rules`,
      });
    }

    return globs;
  }

  /**
   * Generate a glob-specific rule file.
   */
  private generateGlobFile(_ast: Program, config: GlobConfig): FormatterOutput | null {
    const sections: string[] = [];

    // Frontmatter with globs
    const fm = [
      '---',
      `description: "${config.description}"`,
      `globs:`,
      ...config.patterns.map((p) => `  - "${p}"`),
      '---',
    ];
    sections.push(fm.join('\n'));

    if (config.content) {
      const dedentedContent = this.dedent(config.content);
      const normalizedContent = this.normalizeMarkdownForPrettier(dedentedContent);
      sections.push(normalizedContent);
    }

    // Only return if we have content beyond frontmatter
    if (sections.length <= 1) return null;

    return {
      path: `.cursor/rules/${config.name}.mdc`,
      content: sections.join('\n\n') + '\n',
    };
  }

  /**
   * Generate command files for multi-line shortcuts.
   *
   * Shortcuts with multi-line content (containing newlines) are converted
   * to individual `.cursor/commands/*.md` files that can be invoked as
   * slash commands in Cursor 1.6+.
   *
   * Single-line shortcuts remain as documentation in the rules file.
   *
   * @see https://cursor.com/changelog/1-6
   */
  private generateCommandFiles(ast: Program): FormatterOutput[] {
    const block = this.findBlock(ast, 'shortcuts');
    if (!block) return [];

    const props = this.getProps(block.content);
    const commands: FormatterOutput[] = [];

    for (const [name, value] of Object.entries(props)) {
      let content = '';

      // Handle object shortcuts with prompt/content properties (but not TextContent AST nodes)
      if (
        value &&
        typeof value === 'object' &&
        !Array.isArray(value) &&
        !('type' in value && (value as Record<string, unknown>)['type'] === 'TextContent')
      ) {
        const obj = value as Record<string, Value>;
        if (obj['prompt'] === true || obj['content']) {
          content = obj['content'] ? this.valueToString(obj['content']) : '';
        }
      } else {
        content = this.valueToString(value);
      }

      // Only multi-line content becomes a command file
      if (content.includes('\n')) {
        // Remove leading slash from command name
        const fileName = name.replace(/^\//, '');
        commands.push({
          path: `.cursor/commands/${fileName}.md`,
          content: content.trim(),
        });
      }
    }

    return commands;
  }

  /**
   * Add common sections shared between modern and legacy formats.
   */
  private addCommonSections(ast: Program, sections: string[]): void {
    const intro = this.intro(ast);
    if (intro) sections.push(intro);

    const techStack = this.techStack(ast);
    if (techStack) sections.push(techStack);

    const architecture = this.architecture(ast);
    if (architecture) sections.push(architecture);

    const codeStyle = this.codeStyle(ast);
    if (codeStyle) sections.push(codeStyle);

    const gitCommits = this.gitCommits(ast);
    if (gitCommits) sections.push(gitCommits);

    const configFiles = this.configFiles(ast);
    if (configFiles) sections.push(configFiles);

    const commands = this.commands(ast);
    if (commands) sections.push(commands);

    const devCommands = this.devCommands(ast);
    if (devCommands) sections.push(devCommands);

    const postWork = this.postWork(ast);
    if (postWork) sections.push(postWork);

    const documentation = this.documentation(ast);
    if (documentation) sections.push(documentation);

    const diagrams = this.diagrams(ast);
    if (diagrams) sections.push(diagrams);

    const knowledge = this.knowledgeContent(ast);
    if (knowledge) sections.push(knowledge);

    const examples = this.examples(ast);
    if (examples) sections.push(examples);

    const never = this.never(ast);
    if (never) sections.push(never);
  }

  private intro(ast: Program): string {
    // First check if identity block has rich content that should be used in full
    const identity = this.findBlock(ast, 'identity');
    if (identity) {
      const fullText = this.dedent(this.extractText(identity.content));
      // If identity starts with "You are", use the full content
      if (fullText.toLowerCase().startsWith('you are')) {
        return fullText;
      }
    }

    // Fall back to @context MixedContent text (project description alongside properties)
    if (!identity) {
      const context = this.findBlock(ast, 'context');
      if (context?.content.type === 'MixedContent' && context.content.text) {
        const text = this.dedent(context.content.text.value.trim());
        if (text) return text;
      }
    }

    // Otherwise use extracted project info for a generated intro
    const projectInfo = this.extractProjectInfo(ast);
    const orgSuffix = projectInfo.org ? ` at ${projectInfo.org}` : '';
    return `You are working on ${projectInfo.text}${orgSuffix}.`;
  }

  /**
   * Generate YAML frontmatter for Cursor MDC format.
   *
   * **Known limitation**: `alwaysApply` is always emitted as `true`, meaning
   * this formatter can only produce "Always" rules. "Agent Requested" rules
   * (description-only, no `alwaysApply`) and "Auto Attached" rules
   * (globs-only) are not currently supported. This is a medium-priority
   * improvement tracked in docs/agent-compat/cursor-plan.md.
   *
   * @see https://cursor.com/docs/context/rules
   */
  private frontmatter(ast: Program): string {
    const description = this.extractProjectDescription(ast);
    const lines = ['---', `description: "${description}"`, 'alwaysApply: true', '---'];
    return lines.join('\n');
  }

  private extractProjectDescription(ast: Program): string {
    // Try to get project name from meta
    const name = this.getMetaField(ast, 'name');
    if (name) {
      return `Project rules for ${name}`;
    }

    // Try to extract from context.project
    const context = this.findBlock(ast, 'context');
    if (context) {
      const projectText = this.getProp(context.content, 'project');
      if (projectText && typeof projectText === 'string') {
        // Extract project name from description (first noun/name)
        const regex = /working on\s+(\w+)/i;
        const match = regex.exec(projectText);
        if (match) {
          return `Project rules for ${match[1]}`;
        }
      }
    }

    return 'Project-specific rules';
  }

  private extractProjectInfo(ast: Program): { text: string; org: string | null } {
    const org = this.getMetaField(ast, 'org') ?? null;

    // Try context.project first (new structure)
    const context = this.findBlock(ast, 'context');
    if (context) {
      const projectText = this.getProp(context.content, 'project');
      if (projectText && typeof projectText === 'string') {
        return { text: projectText.trim(), org };
      }
    }

    // Fall back to identity block
    const identity = this.findBlock(ast, 'identity');
    if (identity) {
      const text = this.extractText(identity.content);
      const firstLine = text.split('\n')[0]?.trim();
      if (firstLine) {
        return { text: firstLine, org };
      }
    }

    return { text: 'the project', org };
  }

  private techStack(ast: Program): string | null {
    const items = this.extractTechStackItems(ast);
    return items.length > 0 ? `Tech stack: ${items.join(', ')}` : null;
  }

  private extractTechStackItems(ast: Program): string[] {
    const items: string[] = [];
    const context = this.findBlock(ast, 'context');

    if (context) {
      this.extractTechFromContext(context.content, items);
    }

    // Fall back to standards.code structure
    if (items.length === 0) {
      this.extractTechFromStandards(ast, items);
    }

    return items;
  }

  private extractTechFromContext(content: Block['content'], items: string[]): void {
    // Try tech-stack key first
    const tech = this.getProp(content, 'tech-stack');
    if (tech) items.push(...this.extractListItems(tech));

    // Also check individual fields
    const languages = this.getProp(content, 'languages');
    if (languages) this.collectValueItems(items, languages);

    const runtime = this.getProp(content, 'runtime');
    if (runtime) items.push(this.valueToString(runtime));

    const monorepo = this.getProp(content, 'monorepo');
    if (monorepo && typeof monorepo === 'object' && !Array.isArray(monorepo)) {
      const mono = monorepo as Record<string, Value>;
      if (mono['tool']) items.push(`${this.valueToString(mono['tool'])}`);
      if (mono['packageManager'])
        items.push(`${this.valueToString(mono['packageManager'])} workspaces`);
    }
  }

  private extractTechFromStandards(ast: Program, items: string[]): void {
    const standards = this.findBlock(ast, 'standards');
    if (!standards) return;

    const code = this.getProp(standards.content, 'code');
    if (code && typeof code === 'object' && !Array.isArray(code)) {
      const codeObj = code as Record<string, Value>;
      this.collectValueItems(items, codeObj['languages']);
      this.collectValueItems(items, codeObj['frameworks']);
      this.collectValueItems(items, codeObj['testing']);
    }
  }

  private architecture(ast: Program): string | null {
    const context = this.findBlock(ast, 'context');
    if (!context) return null;

    // First try explicit architecture property
    const arch = this.getProp(context.content, 'architecture');
    if (arch) {
      const text = typeof arch === 'string' ? arch : this.valueToString(arch);
      if (text.trim()) return `Architecture:\n${text.trim()}`;
    }

    // Fall back to extracting text which may contain architecture info
    const text = this.extractText(context.content);
    if (text?.includes('Architecture')) {
      return text.trim();
    }

    return null;
  }

  private codeStyle(ast: Program): string | null {
    const items = this.extractCodeStyleItems(ast);
    if (items.length === 0) return null;
    return `Code style:\n${items.map((i) => '- ' + i).join('\n')}`;
  }

  private extractCodeStyleItems(ast: Program): string[] {
    // Use shared extractor for consistent handling of all @standards keys
    const standardsBlock = this.findBlock(ast, 'standards');
    if (!standardsBlock) return [];

    const extracted = this.standardsExtractor.extract(standardsBlock.content);
    const items: string[] = [];

    // Collect all items from extracted code standards (dynamically iterates ALL keys)
    for (const entry of extracted.codeStandards.values()) {
      items.push(...entry.items);
    }

    return items;
  }

  private gitCommits(ast: Program): string | null {
    // Try @standards.git first
    const standardsBlock = this.findBlock(ast, 'standards');
    if (standardsBlock) {
      const git = this.getProp(standardsBlock.content, 'git');
      if (git && typeof git === 'object' && !Array.isArray(git)) {
        const items = this.extractGitItems(git as Record<string, Value>);
        if (items.length > 0) {
          return `Git Commits:\n${items.map((i) => '- ' + i).join('\n')}`;
        }
      }
    }

    // Fall back to context.git-commits
    const context = this.findBlock(ast, 'context');
    if (context) {
      const commits = this.getProp(context.content, 'git-commits');
      if (commits && typeof commits === 'object' && !Array.isArray(commits)) {
        const items = this.extractGitItems(commits as Record<string, Value>);
        if (items.length > 0) {
          return `Git Commits:\n${items.map((i) => '- ' + i).join('\n')}`;
        }
      }
    }

    return null;
  }

  private extractGitItems(obj: Record<string, Value>): string[] {
    const items: string[] = [];
    for (const [key, value] of Object.entries(obj)) {
      if (key === 'types' && Array.isArray(value)) {
        const types = value.map((v) => this.valueToString(v)).join(', ');
        items.push(`types: ${types}`);
      } else if (typeof value === 'string') {
        items.push(`${key}: ${value}`);
      } else if (typeof value === 'number') {
        items.push(`${key}: ${value}`);
      }
    }
    return items;
  }

  private configFiles(ast: Program): string | null {
    const items = this.extractConfigItems(ast);
    if (items.length === 0) return null;
    return `Config:\n${items.map((i) => '- ' + i).join('\n')}`;
  }

  private extractConfigItems(ast: Program): string[] {
    // Try @standards.config first
    const standardsBlock = this.findBlock(ast, 'standards');
    if (standardsBlock) {
      const config = this.getProp(standardsBlock.content, 'config');
      const items = this.extractConfigFromObject(config);
      if (items.length > 0) return items;
    }

    // Fall back to context.configuration-files
    const context = this.findBlock(ast, 'context');
    if (context) {
      const config = this.getProp(context.content, 'configuration-files');
      return this.extractConfigFromObjectWithList(config);
    }

    return [];
  }

  private extractConfigFromObject(config: Value | undefined): string[] {
    if (!config || typeof config !== 'object' || Array.isArray(config)) return [];
    const items: string[] = [];
    for (const [key, value] of Object.entries(config as Record<string, Value>)) {
      if (typeof value === 'string') {
        items.push(`${key}: ${value}`);
      }
    }
    return items;
  }

  private extractConfigFromObjectWithList(config: Value | undefined): string[] {
    if (!config || typeof config !== 'object' || Array.isArray(config)) return [];
    const items: string[] = [];
    for (const [key, value] of Object.entries(config as Record<string, Value>)) {
      const valueItems = this.extractListItems(value);
      if (valueItems.length > 0) {
        items.push(`${key}: ${valueItems.join('; ')}`);
      }
    }
    return items;
  }

  private commands(ast: Program): string | null {
    const block = this.findBlock(ast, 'shortcuts');
    if (!block) return null;

    const props = this.getProps(block.content);
    if (Object.keys(props).length === 0) return null;

    const lines: string[] = [];
    for (const [cmd, desc] of Object.entries(props)) {
      const descObj = desc as Record<string, Value>;
      const shortDesc =
        desc &&
        typeof desc === 'object' &&
        !Array.isArray(desc) &&
        !('type' in desc && (desc as Record<string, unknown>)['type'] === 'TextContent')
          ? descObj['description']
            ? this.valueToString(descObj['description']!)
            : ''
          : (this.valueToString(desc).split('\n')[0] ?? '');
      lines.push(`${cmd} - ${shortDesc}`);
    }

    return lines.length > 0 ? `Commands:\n${lines.join('\n')}` : null;
  }

  private devCommands(ast: Program): string | null {
    const knowledge = this.findBlock(ast, 'knowledge');
    if (!knowledge) return null;

    const text = this.extractText(knowledge.content);
    const match = this.extractSectionWithCodeBlock(text, '## Development Commands');
    if (!match) return null;

    const content = match.replace('## Development Commands', '').trim();
    return `Development Commands:\n${content}`;
  }

  private postWork(ast: Program): string | null {
    const knowledge = this.findBlock(ast, 'knowledge');
    if (!knowledge) return null;

    const text = this.extractText(knowledge.content);
    const match = this.extractSectionWithCodeBlock(text, '## Post-Work Verification');
    if (!match) return null;

    const content = match.replace('## Post-Work Verification', '').trim();
    return `Post-Work Verification:\n${content}`;
  }

  private documentation(ast: Program): string | null {
    const items = this.extractDocItems(ast);
    if (items.length === 0) return null;
    return `Documentation:\n${items.map((i) => '- ' + i).join('\n')}`;
  }

  private extractDocItems(ast: Program): string[] {
    // Try @standards.documentation first
    const standardsBlock = this.findBlock(ast, 'standards');
    if (standardsBlock) {
      const docs = this.getProp(standardsBlock.content, 'documentation');
      const items = this.extractStringValuesFromObject(docs);
      if (items.length > 0) return items;
    }

    // Fall back to context.documentation-verification
    const context = this.findBlock(ast, 'context');
    if (context) {
      const docs = this.getProp(context.content, 'documentation-verification');
      if (docs) return this.extractListItems(docs);
    }

    return [];
  }

  private extractStringValuesFromObject(obj: Value | undefined): string[] {
    if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return [];
    const items: string[] = [];
    for (const value of Object.values(obj as Record<string, Value>)) {
      if (typeof value === 'string') items.push(value);
    }
    return items;
  }

  private diagrams(ast: Program): string | null {
    const items = this.extractDiagramItems(ast);
    if (items.length === 0) return null;
    return `Diagrams:\n${items.map((i) => '- ' + i).join('\n')}`;
  }

  private extractDiagramItems(ast: Program): string[] {
    // Try @standards.diagrams first
    const standardsBlock = this.findBlock(ast, 'standards');
    if (standardsBlock) {
      const diag = this.getProp(standardsBlock.content, 'diagrams');
      const items = this.parseDiagramObject(diag, 'format');
      if (items.length > 0) return items;
    }

    // Fall back to context.diagrams
    const context = this.findBlock(ast, 'context');
    if (context) {
      const diag = this.getProp(context.content, 'diagrams');
      return this.parseDiagramObject(diag, 'tool');
    }

    return [];
  }

  private parseDiagramObject(diag: Value | undefined, formatKey: string): string[] {
    if (!diag || typeof diag !== 'object' || Array.isArray(diag)) return [];
    const diagObj = diag as Record<string, Value>;
    const items: string[] = [];
    if (diagObj[formatKey]) items.push(`Use ${this.valueToString(diagObj[formatKey])}`);
    if (diagObj['types'] && Array.isArray(diagObj['types'])) {
      items.push(`Types: ${diagObj['types'].map((t) => this.valueToString(t)).join(', ')}`);
    }
    return items;
  }

  private knowledgeContent(ast: Program): string | null {
    const knowledge = this.findBlock(ast, 'knowledge');
    if (!knowledge) return null;

    const text = this.extractText(knowledge.content);
    if (!text) return null;

    const consumedHeaders = ['## Development Commands', '## Post-Work Verification'];
    let remaining = text;

    for (const header of consumedHeaders) {
      const headerIndex = remaining.indexOf(header);
      if (headerIndex === -1) continue;

      const afterHeader = remaining.indexOf('\n', headerIndex);
      if (afterHeader === -1) {
        remaining = remaining.substring(0, headerIndex).trimEnd();
        continue;
      }

      const nextSection = remaining.indexOf('\n## ', afterHeader);
      if (nextSection === -1) {
        remaining = remaining.substring(0, headerIndex).trimEnd();
      } else {
        remaining = remaining.substring(0, headerIndex) + remaining.substring(nextSection + 1);
      }
    }

    remaining = remaining.trim();
    if (!remaining) return null;

    return this.stripAllIndent(remaining);
  }

  private examples(ast: Program): string | null {
    const examples = this.extractExamples(ast);
    if (examples.length === 0) return null;

    const parts: string[] = ['Examples:'];

    for (const example of examples) {
      parts.push(`\n### Example: ${example.name}`);
      if (example.description) {
        const safeDescription = example.description.replace(/[\r\n]+/g, ' ').trim();
        parts.push(safeDescription);
      }
      parts.push('');
      parts.push('**Input:**');
      parts.push(this.renderCodeFence(this.dedent(example.input)));
      parts.push('');
      parts.push('**Output:**');
      parts.push(this.renderCodeFence(this.dedent(example.output)));
    }

    return parts.join('\n');
  }

  private never(ast: Program): string | null {
    const block = this.findBlock(ast, 'restrictions');
    if (!block) return null;

    const items = this.extractNeverItems(block.content);
    if (items.length === 0) return null;

    return `Never:\n${items.map((i) => '- ' + i).join('\n')}`;
  }

  private extractNeverItems(content: Block['content']): string[] {
    if (content.type === 'ArrayContent') {
      return content.elements.map((item: Value) => this.valueToString(item));
    }

    if (content.type === 'TextContent') {
      return content.value
        .trim()
        .split('\n')
        .map((line: string) => line.trim().replace(/^-\s*/, ''))
        .filter((line: string) => line.length > 0);
    }

    if (content.type === 'ObjectContent') {
      const itemsArray = this.getProp(content, 'items');
      if (Array.isArray(itemsArray)) {
        return itemsArray.map((item: unknown) => this.valueToString(item as Value));
      }
    }

    return [];
  }

  private extractListItems(value: Value): string[] {
    if (Array.isArray(value)) {
      return value.map((v) => this.valueToString(v));
    }
    if (typeof value === 'object' && value !== null) {
      const obj = value as Record<string, Value>;
      const items: string[] = [];
      for (const [key, val] of Object.entries(obj)) {
        items.push(`${key}: ${this.valueToString(val)}`);
      }
      return items;
    }
    if (typeof value === 'string') {
      return value
        .split('\n')
        .map((l) => l.trim().replace(/^-\s*/, ''))
        .filter((l) => l.length > 0);
    }
    return [this.valueToString(value)];
  }

  private collectValueItems(items: string[], value: Value | undefined): void {
    if (!value) return;
    const arr = Array.isArray(value) ? value : [value];
    for (const item of arr) {
      items.push(this.valueToString(item));
    }
  }
}
