import assert from 'node:assert/strict';
import test from 'node:test';

import { isSceneGraphEdgeRejected } from './sceneGraphEdgeRules';
import {
  canSetSideStoryStart,
  getNodeStoryRoot,
  isNodeInSideStoryline,
  isSideStoryEdge,
  listSideStoryStarts,
} from './sceneGraphLineage';
import type { SceneGraphEdge, SceneGraphNode } from '../types';
import { asGraphNodeId, asSceneId } from '../types/ids';

function node(id: string, scene: string, opts?: Partial<SceneGraphNode>): SceneGraphNode {
  return {
    id: asGraphNodeId(id),
    sceneId: asSceneId(scene),
    x: 0,
    y: 0,
    isStartScene: false,
    isSideStoryStart: false,
    sideStoryLineTitle: '',
    ...opts,
  };
}

function edge(id: string, src: string, tgt: string): SceneGraphEdge {
  return { id, sourceGraphNodeId: asGraphNodeId(src), targetGraphNodeId: asGraphNodeId(tgt) };
}

void test('canSetSideStoryStart: false when main or side marker in component', () => {
  const nodes = [
    node('main', 's1', { isStartScene: true }),
    node('a', 's2'),
    node('side', 's3', { isSideStoryStart: true }),
    node('b', 's4'),
    node('orphan', 's5'),
  ];
  const edges = [edge('e1', 'main', 'a'), edge('e2', 'side', 'b')];
  assert.equal(canSetSideStoryStart(nodes, edges, asGraphNodeId('a')), false);
  assert.equal(canSetSideStoryStart(nodes, edges, asGraphNodeId('b')), false);
  assert.equal(canSetSideStoryStart(nodes, edges, asGraphNodeId('orphan')), true);
});

void test('getNodeStoryRoot classifies main, side, orphan', () => {
  const nodes = [
    node('main', 's1', { isStartScene: true }),
    node('a', 's2'),
    node('side', 's3', { isSideStoryStart: true }),
    node('b', 's4'),
    node('orphan', 's5'),
  ];
  const edges = [edge('e1', 'main', 'a'), edge('e2', 'side', 'b')];
  assert.equal(getNodeStoryRoot(nodes, edges, asGraphNodeId('main')), 'main');
  assert.equal(getNodeStoryRoot(nodes, edges, asGraphNodeId('a')), 'main');
  assert.equal(getNodeStoryRoot(nodes, edges, asGraphNodeId('b')), asGraphNodeId('side'));
  assert.equal(getNodeStoryRoot(nodes, edges, asGraphNodeId('orphan')), null);
  assert.equal(isNodeInSideStoryline(nodes, edges, asGraphNodeId('main')), false);
  assert.equal(isNodeInSideStoryline(nodes, edges, asGraphNodeId('a')), false);
  assert.equal(isNodeInSideStoryline(nodes, edges, asGraphNodeId('b')), true);
});

void test('isSceneGraphEdgeRejected: blocks main↔side and side↔side', () => {
  const nodes = [
    node('main', 's1', { isStartScene: true }),
    node('side1', 's2', { isSideStoryStart: true }),
    node('side2', 's3', { isSideStoryStart: true }),
    node('a', 's4'),
    node('b', 's5'),
  ];
  const edges = [edge('e1', 'side1', 'a'), edge('e2', 'side2', 'b')];
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('main'), asGraphNodeId('side1')), true);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('side1'), asGraphNodeId('main')), true);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('main'), asGraphNodeId('a')), true);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('a'), asGraphNodeId('main')), true);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('a'), asGraphNodeId('b')), true);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('a'), asGraphNodeId('side1')), false);
});

void test('isSceneGraphEdgeRejected: blocks main to any node in side component without badge', () => {
  const nodes = [
    node('main', 's1', { isStartScene: true }),
    node('side1', 's2', { isSideStoryStart: true }),
    node('a', 's3'),
    node('b', 's4'),
  ];
  const edges = [edge('e1', 'side1', 'a'), edge('e2', 'a', 'b')];
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('main'), asGraphNodeId('a')), true);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('main'), asGraphNodeId('b')), true);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('a'), asGraphNodeId('main')), true);
});

void test('isSideStoryEdge: side chain edges are blue even without badge on endpoint', () => {
  const nodes = [
    node('side', 's1', { isSideStoryStart: true }),
    node('a', 's2'),
    node('b', 's3'),
  ];
  const edges = [edge('e1', 'side', 'a'), edge('e2', 'a', 'b')];
  assert.equal(isSideStoryEdge(nodes, edges, edges[0]!), true);
  assert.equal(isSideStoryEdge(nodes, edges, edges[1]!), true);
});

void test('isNodeInSideStoryline: follows current component after reconnect', () => {
  const nodes = [
    node('main', 's1', { isStartScene: true }),
    node('side', 's2', { isSideStoryStart: true }),
    node('x', 's3'),
  ];
  const mainEdge = [edge('e1', 'main', 'x')];
  const sideEdge = [edge('e2', 'side', 'x')];
  assert.equal(isNodeInSideStoryline(nodes, mainEdge, asGraphNodeId('x')), false);
  assert.equal(isNodeInSideStoryline(nodes, sideEdge, asGraphNodeId('x')), true);
});

void test('isSideStoryEdge: main edges are not side edges', () => {
  const nodes = [
    node('main', 's1', { isStartScene: true }),
    node('a', 's2'),
    node('side', 's3', { isSideStoryStart: true }),
    node('b', 's4'),
  ];
  const edges = [edge('e1', 'main', 'a'), edge('e2', 'side', 'b')];
  assert.equal(isSideStoryEdge(nodes, edges, edges[0]!), false);
  assert.equal(isSideStoryEdge(nodes, edges, edges[1]!), true);
});

void test('isSceneGraphEdgeRejected: allows main-to-main edges', () => {
  const nodes = [node('main', 's1', { isStartScene: true }), node('a', 's2')];
  const edges: SceneGraphEdge[] = [];
  assert.equal(
    isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('main'), asGraphNodeId('a')),
    false,
  );
});

void test('isSceneGraphEdgeRejected: allows main to free connected chain without markers', () => {
  const nodes = [
    node('main', 's1', { isStartScene: true }),
    node('a', 's2'),
    node('b', 's3'),
    node('c', 's4'),
  ];
  const edges = [edge('e1', 'main', 'a'), edge('e2', 'b', 'c')];
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('main'), asGraphNodeId('b')), false);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('a'), asGraphNodeId('b')), false);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('a'), asGraphNodeId('c')), false);
});

void test('isSceneGraphEdgeRejected: allows connections within main component without start badge on target', () => {
  const nodes = [
    node('main', 's1', { isStartScene: true }),
    node('a', 's2'),
    node('b', 's3'),
    node('c', 's4'),
  ];
  const edges = [edge('e1', 'main', 'a'), edge('e2', 'a', 'b')];
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('main'), asGraphNodeId('b')), false);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('a'), asGraphNodeId('c')), false);
  assert.equal(isSceneGraphEdgeRejected(nodes, edges, asGraphNodeId('b'), asGraphNodeId('c')), false);
});

void test('listSideStoryStarts returns all side starters', () => {
  const nodes = [
    node('side1', 's1', { isSideStoryStart: true, sideStoryLineTitle: 'Quest A' }),
    node('side2', 's2', { isSideStoryStart: true }),
  ];
  assert.equal(listSideStoryStarts(nodes).length, 2);
});
