import { describe, it, expect } from 'vitest';
import {
  resolveNodeOutputField,
  declaredFieldsFromSchema,
  OutputRefError,
} from '../output-ref.js';

describe('resolveNodeOutputField', () => {
  describe('with declared schema match', () => {
    it('returns value when field exists in output and schema', () => {
      const declaredFields = new Set(['name', 'status']);
      const output = { name: 'test-result', status: 'completed' };
      const result = resolveNodeOutputField(output, 'node-1', 'name', declaredFields);
      expect(result).toEqual({ kind: 'value', value: 'test-result' });
    });

    it('returns JSON-serialized value for non-string fields', () => {
      const declaredFields = new Set(['count']);
      const output = { count: 42 };
      const result = resolveNodeOutputField(output, 'node-1', 'count', declaredFields);
      expect(result).toEqual({ kind: 'value', value: '42' });
    });

    it('returns JSON-serialized value for object fields', () => {
      const declaredFields = new Set(['data']);
      const output = { data: { key: 'val' } };
      const result = resolveNodeOutputField(output, 'node-1', 'data', declaredFields);
      expect(result).toEqual({ kind: 'value', value: '{"key":"val"}' });
    });
  });

  describe('with schemaless JSON output', () => {
    it('returns value when field exists in output without schema', () => {
      const output = { dynamic_field: 'hello' };
      const result = resolveNodeOutputField(output, 'node-1', 'dynamic_field');
      expect(result).toEqual({ kind: 'value', value: 'hello' });
    });

    it('returns JSON-serialized number without schema', () => {
      const output = { score: 99 };
      const result = resolveNodeOutputField(output, 'node-1', 'score');
      expect(result).toEqual({ kind: 'value', value: '99' });
    });
  });

  describe('missing optional field', () => {
    it('returns empty when field is declared in schema but missing from output', () => {
      const declaredFields = new Set(['name', 'optional_field']);
      const output = { name: 'test' };
      const result = resolveNodeOutputField(output, 'node-1', 'optional_field', declaredFields);
      expect(result).toEqual({ kind: 'empty', value: '' });
    });

    it('returns empty when field exists but value is null', () => {
      const declaredFields = new Set(['name']);
      const output = { name: null };
      const result = resolveNodeOutputField(output, 'node-1', 'name', declaredFields);
      expect(result).toEqual({ kind: 'empty', value: '' });
    });

    it('returns empty when field exists but value is undefined', () => {
      const declaredFields = new Set(['name']);
      const output = { name: undefined };
      const result = resolveNodeOutputField(output, 'node-1', 'name', declaredFields);
      expect(result).toEqual({ kind: 'empty', value: '' });
    });
  });

  describe('missing required field', () => {
    it('throws OutputRefError when field is not declared and not in output', () => {
      const output = { name: 'test' };
      expect(() => resolveNodeOutputField(output, 'node-1', 'nonexistent')).toThrow(OutputRefError);
    });

    it('throws OutputRefError with nodeId and field info', () => {
      const output = { name: 'test' };
      try {
        resolveNodeOutputField(output, 'my-node', 'missing_field');
        expect.unreachable('Should have thrown');
      } catch (err) {
        expect(err).toBeInstanceOf(OutputRefError);
        if (err instanceof OutputRefError) {
          expect(err.nodeId).toBe('my-node');
          expect(err.field).toBe('missing_field');
        }
      }
    });

    it('includes available fields in error message', () => {
      const output = { name: 'test', status: 'ok' };
      try {
        resolveNodeOutputField(output, 'node-1', 'nonexistent');
        expect.unreachable('Should have thrown');
      } catch (err) {
        expect(err).toBeInstanceOf(OutputRefError);
        if (err instanceof OutputRefError) {
          expect(err.message).toContain('name');
          expect(err.message).toContain('status');
        }
      }
    });
  });
});

describe('declaredFieldsFromSchema', () => {
  it('extracts fields from a valid JSON Schema object', () => {
    const schema = {
      type: 'object',
      properties: {
        name: { type: 'string' },
        count: { type: 'number' },
      },
    };
    const fields = declaredFieldsFromSchema(schema);
    expect(fields).toEqual(new Set(['name', 'count']));
  });

  it('returns empty set for undefined schema', () => {
    const fields = declaredFieldsFromSchema(undefined);
    expect(fields).toEqual(new Set());
  });

  it('returns empty set for string schema', () => {
    const fields = declaredFieldsFromSchema('just a string description');
    expect(fields).toEqual(new Set());
  });

  it('returns empty set for schema without properties', () => {
    const fields = declaredFieldsFromSchema({ type: 'object' });
    expect(fields).toEqual(new Set());
  });
});