// resolveAnchorMap 회귀 테스트 (플랜 ds-outline-anchor-b5 g5-t1 / NEW-5 / B4 / C1).
// 실행: node --test src/lib/utils/resolveAnchorMap.test.ts
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { resolveAnchorMap, isJumpTargetCandidate } from './resolveAnchorMap.ts';
import { type DocumentSection } from './headingPath.ts';

let _id = 0;
function sec(p: Partial<DocumentSection>): DocumentSection {
  return {
    chunk_id: ++_id,
    section_title: null,
    heading_path: null,
    level: null,
    node_type: null,
    is_leaf: true,
    char_start: null,
    section_type: null,
    summary: null,
    confidence: null,
    ...p,
  };
}

const LONG = 'x'.repeat(500);

test('trustBE=false → 무조건 string-match 폴백(fellBack=true)', () => {
  const md = '# Alpha\nbody\n# Beta\nx';
  const secs = [sec({ section_title: 'Alpha', char_start: 999 }), sec({ section_title: 'Beta', char_start: 999 })];
  const r = resolveAnchorMap(md, secs, { trustBE: false });
  assert.equal(r.fellBack, true);
  // char_start(999) 무시하고 string-match offset 사용
  assert.ok(Object.values(r.anchors).every((o) => o < 50));
});

test('trustBE=true + 모든 jump-target candidate char_start 보유 → BE 채택(fellBack=false)', () => {
  const secs = [
    sec({ section_title: 'A', char_start: 5, is_leaf: true }),
    sec({ section_title: 'B', char_start: 42, is_leaf: true }),
  ];
  const r = resolveAnchorMap(LONG, secs, { trustBE: true });
  assert.equal(r.fellBack, false);
  assert.equal(r.anchors[secs[0].chunk_id], 5);
  assert.equal(r.anchors[secs[1].chunk_id], 42);
  assert.equal(r.matched, 2);
});

test('[NEW-5] windowed doc — window-child char_start NULL 이 폴백을 유발하지 않음(split-parent BE 사용)', () => {
  const secs = [
    sec({ section_title: 'Big', heading_path: 'Big', node_type: 'chapter_split', is_leaf: false, char_start: 10 }),
    sec({ section_title: 'Big', heading_path: 'Big', node_type: 'window', is_leaf: true, char_start: null }),
    sec({ section_title: 'Big', heading_path: 'Big', node_type: 'window', is_leaf: true, char_start: null }),
  ];
  const r = resolveAnchorMap(LONG, secs, { trustBE: true });
  // window-child NULL 은 candidate 가 아니므로 트리거 안 됨 → BE 사용, split-parent 점프 보존
  assert.equal(r.fellBack, false, 'window-child NULL 이 whole-doc 폴백을 유발하면 안 됨(NEW-5)');
  assert.equal(r.anchors[secs[0].chunk_id], 10, 'split-parent char_start 가 BE 맵에 있어야 함');
  // window-child 는 anchor 없음
  assert.equal(r.anchors[secs[1].chunk_id], undefined);
});

test('[B4] non-PASS doc — jump-target candidate char_start NULL → string-match 폴백', () => {
  const md = '# Gamma\nbody text here\n# Delta\nmore';
  const secs = [
    sec({ section_title: 'Gamma', is_leaf: true, char_start: null }),
    sec({ section_title: 'Delta', is_leaf: true, char_start: null }),
  ];
  const r = resolveAnchorMap(md, secs, { trustBE: true });
  assert.equal(r.fellBack, true, 'candidate char_start NULL 이면 폴백해야 함(BE-first not BE-only)');
  // string-match 로 실제 jump 산출(0 아님)
  assert.ok(r.matched >= 1, 'md-aligned doc 는 폴백 string-match 로 jump 비-0');
});

test('char_start > splicedText.length → 그 anchor 만 비활성, 폴백 안 함', () => {
  const secs = [
    sec({ section_title: 'A', char_start: 3, is_leaf: true }),
    sec({ section_title: 'B', char_start: 100000, is_leaf: true }), // 범위 초과(truncated tail)
  ];
  const short = 'hello world';
  const r = resolveAnchorMap(short, secs, { trustBE: true });
  assert.equal(r.fellBack, false, '범위 초과는 폴백 트리거 아님(candidate char_start NOT NULL)');
  assert.equal(r.anchors[secs[0].chunk_id], 3);
  assert.equal(r.anchors[secs[1].chunk_id], undefined, '초과 anchor 는 비활성');
});

test('preamble(title 없음, is_leaf) char_start NULL 은 candidate 아님 → 폴백 유발 X', () => {
  const secs = [
    sec({ section_title: null, heading_path: null, is_leaf: true, char_start: null }), // preamble
    sec({ section_title: 'Real', is_leaf: true, char_start: 7 }),
  ];
  const r = resolveAnchorMap(LONG, secs, { trustBE: true });
  assert.equal(isJumpTargetCandidate(secs[0]), false, 'preamble 은 candidate 아님');
  assert.equal(r.fellBack, false);
  assert.equal(r.anchors[secs[1].chunk_id], 7);
});
