import { describe, it, expect } from 'vitest';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
import { remarkMeldDirectives } from '../src/plugins/remarkMeldDirectives';
import { remarkProcessMeldNodes } from '../src/plugins/remarkProcessMeldNodes';
import { remarkMeldDirectiveHandler } from '../src/plugins/remarkMeldDirectiveHandler';
import path from 'path';

describe('remarkMeldDirectives', () => {
  it('should parse @cmd directives', async () => {
    const content = 'Here is a command: @cmd[echo "hello"]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    expect(String(file).trim()).toBe(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(1);
    const node = meldNodes[0];
    expect(node.meldType).toBe('cmd');
    expect(node.data.command).toBe('echo "hello"');
  }, 500);

  it('should parse @import directives with heading', async () => {
    const content = 'Import section: @import[test.md # Section]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    expect(String(file).trim()).toBe(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(1);
    const node = meldNodes[0];
    expect(node.meldType).toBe('import');
    expect(node.data.importPath).toBe('test.md');
    expect(node.data.heading).toBe('Section');
  }, 500);

  it('should parse @import directives with heading level override', async () => {
    const content = 'Import section: @import[test.md # Section] as ###';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(1);
    const node = meldNodes[0];
    expect(node.meldType).toBe('import');
    expect(node.data.importPath).toBe('test.md');
    expect(node.data.heading).toBe('Section');
    expect(node.data.headingLevelOverride).toBe('###');
  }, 500);

  it('should parse @import directives with heading text override', async () => {
    const content = 'Import section: @import[test.md # Section] as ### "New Title"';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(1);
    const node = meldNodes[0];
    expect(node.meldType).toBe('import');
    expect(node.data.importPath).toBe('test.md');
    expect(node.data.heading).toBe('Section');
    expect(node.data.headingLevelOverride).toBe('###');
    expect(node.data.headingTextOverride).toBe('New Title');
  }, 500);

  it('should parse @import directives with code symbols', async () => {
    const content = 'Import symbols: @import[code.ts # [func1, func2]]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(1);
    const node = meldNodes[0];
    expect(node.meldType).toBe('import');
    expect(node.data.importPath).toBe('code.ts');
    expect(node.data.importSymbolList).toEqual(['func1', 'func2']);
  }, 500);

  it('should parse @import directives with type/interface imports', async () => {
    const content = 'Import type: @import[code.ts # {type}]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(1);
    const node = meldNodes[0];
    expect(node.meldType).toBe('import');
    expect(node.data.importPath).toBe('code.ts');
    expect(node.data.importSymbolList).toEqual(['type']);
  }, 500);

  it('should not parse directives inside code blocks', async () => {
    const content = '```\n@cmd[echo "hi"]\n@import[test.md # Section]\n```';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(0);
    expect(String(file)).toContain('@cmd[echo "hi"]');
    expect(String(file)).toContain('@import[test.md # Section]');
  }, 500);

  it('should parse single-brace symbol imports', async () => {
    const content = 'Import interface: @import[test.ts # {interface}]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(1);
    const node = meldNodes[0];
    expect(node.meldType).toBe('import');
    expect(node.data.importPath).toBe('test.ts');
    expect(node.data.importSymbolList).toEqual(['interface']);
  }, 500);

  it('should handle empty directives gracefully', async () => {
    const content = `
Here is an empty import: @import[]
Here is an empty command: @cmd[]
    `.trim();
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('Error: Invalid import format');
    expect(String(file)).toContain('Error: Invalid command format');
  });

  it('should handle malformed directives gracefully', async () => {
    const content = `
Here is a malformed import: @import[test.md#]
Here is another malformed import: @import[#Section]
Here is a malformed command: @cmd[echo "test
    `.trim();
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('Error: Invalid import format');
    expect(String(file)).toContain('Error: Invalid command format');
  });
});

describe('remarkProcessMeldNodes', () => {
  const testDir = path.join(__dirname, 'fixtures');

  it('should process @cmd directives', async () => {
    const content = 'Output: @cmd[echo "test"]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('Output: test');
  }, 1000);

  it('should handle command errors gracefully', async () => {
    const content = 'Output: @cmd[nonexistentcommand]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('Error: Error processing cmd directive');
    expect((file.data as any).meldErrors.length).toBe(1);
  }, 1000);

  it('should import markdown sections', async () => {
    const content = 'Here is section one: @import[test.md # Section One]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('This is section one content');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should import markdown sections with heading level override', async () => {
    const content = 'Here is section: @import[test.md # Section Two] as ###';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('### Section Two');
    expect(String(file)).toContain('This is section two content');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should import markdown sections with heading text override', async () => {
    const content = 'Here is section: @import[test.md # Section Three] as ### "Custom Title"';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('### Custom Title');
    expect(String(file)).toContain('This is section three content');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should handle missing markdown sections', async () => {
    const content = 'Here is section: @import[test.md # Nonexistent Section]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('Error: Error processing import directive');
    expect((file.data as any).meldErrors.length).toBe(1);
  }, 1000);

  it('should import code symbols', async () => {
    const content = 'Here are functions: @import[test.ts # [testFunction1, testFunction2]]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('function testFunction1()');
    expect(String(file)).toContain('function testFunction2(param: string)');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should import interface definitions', async () => {
    const content = 'Here is interface: @import[test.ts # {interface}]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('interface TestInterface');
    expect(String(file)).toContain('prop1: string');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should import type definitions', async () => {
    const content = 'Here is type: @import[test.ts # {type}]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('type TestType');
    expect(String(file)).toContain('field1: string');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should handle missing code symbols', async () => {
    const content = 'Here is function: @import[test.ts # nonexistentFunction]';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('Error: Error processing import directive');
    expect((file.data as any).meldErrors.length).toBe(1);
  }, 1000);

  it('should handle multiple directives in one file', async () => {
    const content = `
Here is a command: @cmd[echo "test"]
Here is a section: @import[test.md # Section One]
Here is a function: @import[test.ts # testFunction1]
    `.trim();
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('test');
    expect(String(file)).toContain('This is section one content');
    expect(String(file)).toContain('function testFunction1()');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should not process directives inside code blocks', async () => {
    const content = `
Here is some text.

\`\`\`
@cmd[echo "test"]
@import[test.md # Section]
\`\`\`

More text.
    `.trim();
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkStringify)
      .process(content);

    const ast = file.data.ast as any;
    const meldNodes = [];
    const visit = (node: any) => {
      if (node.type === 'meldDirective') {
        meldNodes.push(node);
      }
      if (node.children) {
        node.children.forEach(visit);
      }
    };
    visit(ast);

    expect(meldNodes.length).toBe(0);
    expect(String(file)).toContain('@cmd[echo "test"]');
    expect(String(file)).toContain('@import[test.md # Section]');
  });

  it('should handle precise heading level overrides', async () => {
    const content = `
Here is a level 2 heading: @import[test.md # Section One] as ##
Here is a level 4 heading: @import[test.md # Section Two] as ####
    `.trim();
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkMeldDirectiveHandler)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('## Section One');
    expect(String(file)).toContain('#### Section Two');
    expect((file.data as any).meldErrors.length).toBe(0);
  });

  it('should handle overlapping imports correctly', async () => {
    const content = `
Here is a symbol imported twice:
@import[test.ts # [testFunction1]]
@import[test.ts # testFunction1]
    `.trim();
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    // Both imports should result in the same content
    const matches = String(file).match(/function testFunction1\(\)/g);
    expect(matches).toHaveLength(2);
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should handle very deep heading overrides correctly', async () => {
    const content = 'Deep heading: @import[test.md # Section One] as ######';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    expect(String(file)).toContain('###### Section One');
    expect(String(file)).toContain('This is section one content');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should handle heading text override with special characters correctly', async () => {
    const content = 'Special heading: @import[test.md # Section One] as ### "Title with: * & # special chars!"';
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    // Expect escaped special characters in the output
    expect(String(file)).toContain('### Title with: \\* & # special chars!');
    expect(String(file)).toContain('This is section one content');
    expect((file.data as any).meldErrors.length).toBe(0);
  }, 1000);

  it('should handle malformed directives gracefully in processing', async () => {
    const content = `
Here is a malformed import: @import[#]
Here is another malformed import: @import[]
Here is a malformed command: @cmd[]
    `.trim();
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    // The errors are returned in the content rather than meldErrors
    expect(String(file)).toContain('Error: Invalid import format');
    expect(String(file)).toContain('Error: Invalid command format');
  }, 1000);

  it('should handle malformed symbol imports gracefully in processing', async () => {
    const content = `
Here is a malformed symbol list: @import[test.ts # [func1, ]
Here is another malformed symbol: @import[test.ts # {]
    `.trim();
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkMeldDirectives)
      .use(remarkProcessMeldNodes, { currentFileDir: testDir })
      .use(remarkStringify)
      .process(content);

    // The errors are returned in the content rather than meldErrors
    expect(String(file)).toContain('Error: Invalid import format');
  }, 1000);
}); 