import type { TojuPluginManifest } from '../../../../shared-kernel';
import { resolvePluginLoadOrder } from './plugin-dependency-resolver.logic';

function manifest(id: string, overrides: Partial<TojuPluginManifest> = {}): TojuPluginManifest {
  return {
    apiVersion: '1.0.0',
    compatibility: {
      minimumTojuVersion: '1.0.0'
    },
    description: `${id} plugin`,
    entrypoint: './main.js',
    id,
    kind: 'client',
    schemaVersion: 1,
    title: id,
    version: '1.0.0',
    ...overrides
  };
}

describe('plugin dependency resolver', () => {
  it('orders required dependencies before dependants', () => {
    const featurePlugin = manifest('feature.chat', { relationships: { requires: [{ id: 'library.base' }] } });
    const result = resolvePluginLoadOrder([{ manifest: featurePlugin }, { manifest: manifest('library.base') }]);

    expect(result.blocked).toEqual([]);
    expect(result.ordered.map((entry) => entry.id)).toEqual(['library.base', 'feature.chat']);
  });

  it('uses priority then plugin id for otherwise independent plugins', () => {
    const result = resolvePluginLoadOrder([
      { manifest: manifest('plugin.zed') },
      { manifest: manifest('plugin.bootstrap', { load: { priority: 'bootstrap' } }) },
      { manifest: manifest('plugin.alpha') }
    ]);

    expect(result.ordered.map((entry) => entry.id)).toEqual([
      'plugin.bootstrap',
      'plugin.alpha',
      'plugin.zed'
    ]);
  });

  it('blocks missing dependencies and leaves valid plugins loadable', () => {
    const blockedPlugin = manifest('plugin.blocked', { relationships: { requires: [{ id: 'missing.library' }] } });
    const result = resolvePluginLoadOrder([{ manifest: manifest('plugin.valid') }, { manifest: blockedPlugin }]);

    expect(result.ordered.map((entry) => entry.id)).toEqual(['plugin.valid']);
    expect(result.blocked).toContainEqual({
      message: 'Missing required plugin missing.library',
      pluginId: 'plugin.blocked',
      reason: 'missingDependency'
    });
  });

  it('detects duplicate ids and cycles', () => {
    const result = resolvePluginLoadOrder([
      { manifest: manifest('plugin.duplicate') },
      { manifest: manifest('plugin.duplicate') },
      { manifest: manifest('plugin.a', { relationships: { after: ['plugin.b'] } }) },
      { manifest: manifest('plugin.b', { relationships: { after: ['plugin.a'] } }) }
    ]);

    expect(result.blocked).toEqual(expect.arrayContaining([
      {
        message: 'Duplicate plugin id',
        pluginId: 'plugin.duplicate',
        reason: 'duplicate'
      },
      {
        message: 'Plugin load order contains a cycle',
        pluginId: 'plugin.a',
        reason: 'cycle'
      },
      {
        message: 'Plugin load order contains a cycle',
        pluginId: 'plugin.b',
        reason: 'cycle'
      }
    ]));
  });
});
