import { describe, expect, it } from 'vitest';
import { isProtectedTopic, filterAiActions } from '../graphGuard';

const topic = (id, extras = {}) => ({
  id,
  label: `Topic ${id}`,
  type: 'concept',
  learning_relevance: 'standard',
  relevance_locked: false,
  ...extras,
});

describe('isProtectedTopic', () => {
  it('flags excluded topics', () => {
    expect(isProtectedTopic(topic('a', { learning_relevance: 'exclude' }))).toBe(true);
  });

  it('flags locked topics', () => {
    expect(isProtectedTopic(topic('a', { relevance_locked: true }))).toBe(true);
  });

  it('flags topics that are both excluded and locked', () => {
    expect(
      isProtectedTopic(topic('a', { learning_relevance: 'exclude', relevance_locked: true })),
    ).toBe(true);
  });

  it('does not flag standard / core / peripheral topics', () => {
    expect(isProtectedTopic(topic('a', { learning_relevance: 'core' }))).toBe(false);
    expect(isProtectedTopic(topic('a', { learning_relevance: 'standard' }))).toBe(false);
    expect(isProtectedTopic(topic('a', { learning_relevance: 'peripheral' }))).toBe(false);
  });

  it('treats null/undefined as not protected', () => {
    expect(isProtectedTopic(null)).toBe(false);
    expect(isProtectedTopic(undefined)).toBe(false);
  });
});

describe('filterAiActions', () => {
  const topics = [
    topic('safe'),
    topic('excluded', { learning_relevance: 'exclude' }),
    topic('locked', { relevance_locked: true }),
    topic('both', { learning_relevance: 'exclude', relevance_locked: true }),
    topic('other'),
  ];

  it('drops deletions that target excluded topics', () => {
    const { filtered, dropped } = filterAiActions(topics, {
      deletions: ['safe', 'excluded', 'other'],
      merges: [],
    });
    expect(filtered.deletions).toEqual(['safe', 'other']);
    expect(dropped.deletions).toEqual(['excluded']);
  });

  it('drops deletions that target locked topics', () => {
    const { filtered, dropped } = filterAiActions(topics, {
      deletions: ['locked'],
      merges: [],
    });
    expect(filtered.deletions).toEqual([]);
    expect(dropped.deletions).toEqual(['locked']);
  });

  it('drops merges whose deleteId is protected', () => {
    const merges = [
      { keepId: 'safe',  deleteId: 'other'    },   // ok
      { keepId: 'safe',  deleteId: 'excluded' },   // drop — excluded
      { keepId: 'other', deleteId: 'locked'   },   // drop — locked
      { keepId: 'safe',  deleteId: 'both'     },   // drop — both flags
    ];
    const { filtered, dropped } = filterAiActions(topics, { merges, deletions: [] });
    expect(filtered.merges).toEqual([{ keepId: 'safe', deleteId: 'other' }]);
    expect(dropped.merges).toHaveLength(3);
    expect(dropped.merges.map(m => m.deleteId)).toEqual(['excluded', 'locked', 'both']);
  });

  it('keeps merges where a protected topic is the keepId (canonical survivor)', () => {
    const merges = [
      { keepId: 'excluded', deleteId: 'safe'  },   // ok — protected is survivor
      { keepId: 'locked',   deleteId: 'other' },   // ok — protected is survivor
    ];
    const { filtered, dropped } = filterAiActions(topics, { merges, deletions: [] });
    expect(filtered.merges).toEqual(merges);
    expect(dropped.merges).toEqual([]);
  });

  it('passes through other action keys untouched', () => {
    const actions = {
      deletions: [],
      merges: [],
      newRelations: [{ source: 'a', target: 'b', type: 'related_to' }],
      relevanceUpdates: [{ id: 'safe', learning_relevance: 'peripheral' }],
    };
    const { filtered } = filterAiActions(topics, actions);
    expect(filtered.newRelations).toEqual(actions.newRelations);
    expect(filtered.relevanceUpdates).toEqual(actions.relevanceUpdates);
  });

  it('tolerates missing actions keys', () => {
    const { filtered, dropped } = filterAiActions(topics, {});
    expect(filtered.deletions).toEqual([]);
    expect(filtered.merges).toEqual([]);
    expect(dropped.deletions).toEqual([]);
    expect(dropped.merges).toEqual([]);
  });

  it('tolerates null actions', () => {
    const { filtered, dropped } = filterAiActions(topics, null);
    expect(filtered.deletions).toEqual([]);
    expect(filtered.merges).toEqual([]);
    expect(dropped.deletions).toEqual([]);
    expect(dropped.merges).toEqual([]);
  });

  it('drops references to unknown ids by treating them as not protected (delete pass-through)', () => {
    // If the AI hallucinates an id, the deletion is harmless downstream (no
    // topic by that id exists). We do NOT block on unknown ids — that would
    // mask real bugs. Verify pass-through behavior.
    const { filtered } = filterAiActions(topics, { deletions: ['ghost-id'], merges: [] });
    expect(filtered.deletions).toEqual(['ghost-id']);
  });
});
