// 순수함수 회귀 테스트. 실행(로컬, 의존성 0): node --test src/lib/utils/outlineAnchors.test.ts
// (Node ≥23 또는 22.6+ --experimental-strip-types — TS 타입 네이티브 strip.)
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { buildAnchorMap } from './outlineAnchors.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,
    section_type: null,
    summary: null,
    confidence: null,
    ...p,
  };
}
const md = (lines: string[]) => lines.join('\n');
const lineOff = (lines: string[], idx: number) => {
  let o = 0;
  for (let i = 0; i < idx; i++) o += lines[i].length + 1;
  return o;
};

test('ATX heading 정확 매칭 + offset', () => {
  const lines = ['# 개요', '본문 a', '## 설계 기준', '본문 b'];
  const s = [
    sec({ chunk_id: 101, section_title: '개요' }),
    sec({ chunk_id: 102, section_title: '설계 기준' }),
  ];
  const r = buildAnchorMap(md(lines), s);
  assert.equal(r.anchors[101], lineOff(lines, 0));
  assert.equal(r.anchors[102], lineOff(lines, 2));
  assert.equal(r.matched, 2);
});

test('★ false early match 방어 — 상호참조가 heading 보다 먼저', () => {
  const lines = ['# 개요', '본 절은 Part UW 를 참조한다.', '내용', '# Part UW', '강판'];
  const s = [
    sec({ chunk_id: 1, section_title: '개요' }),
    sec({ chunk_id: 2, section_title: 'Part UW' }),
  ];
  const r = buildAnchorMap(md(lines), s);
  // 상호참조(line 1)가 아니라 실제 heading(line 3)으로
  assert.equal(r.anchors[2], lineOff(lines, 3));
  assert.notEqual(r.anchors[2], lineOff(lines, 1));
});

test('중복 제목 — 단조 커서로 N번째 출현 매칭', () => {
  const lines = ['## General', 'a', '## Scope', 'b', '## General', 'c'];
  const s = [
    sec({ chunk_id: 1, section_title: 'General' }),
    sec({ chunk_id: 2, section_title: 'Scope' }),
    sec({ chunk_id: 3, section_title: 'General' }),
  ];
  const r = buildAnchorMap(md(lines), s);
  assert.equal(r.anchors[1], lineOff(lines, 0)); // 첫 General
  assert.equal(r.anchors[2], lineOff(lines, 2)); // Scope
  assert.equal(r.anchors[3], lineOff(lines, 4)); // 둘째 General (오점프 아님)
});

test('prefix 가드 — 제1조 가 제1조의2 를 오매칭 안 함', () => {
  const lines = ['# 제1조의2', 'x', '# 제1조', 'y'];
  const s = [sec({ chunk_id: 1, section_title: '제1조' })];
  const r = buildAnchorMap(md(lines), s);
  assert.equal(r.anchors[1], lineOff(lines, 2)); // 제1조의2(line0) 아님
});

test('비-ATX 평문 제N조 (전체-라인 매칭)', () => {
  const lines = ['제1조(목적) 이 법은 OO 을 정한다.', '본문', '제2조(정의) 용어는...'];
  const s = [
    sec({ chunk_id: 1, section_title: '제1조(목적) 이 법은 OO 을 정한다.', node_type: 'clause' }),
    sec({ chunk_id: 2, section_title: '제2조(정의) 용어는...', node_type: 'clause' }),
  ];
  const r = buildAnchorMap(md(lines), s);
  assert.equal(r.anchors[1], lineOff(lines, 0));
  assert.equal(r.anchors[2], lineOff(lines, 2));
});

test('window 조각 skip (anchor 없음)', () => {
  const lines = ['## 절', 'aaa', 'bbb'];
  const s = [
    sec({ chunk_id: 1, section_title: '절' }),
    sec({ chunk_id: 2, section_title: '절', node_type: 'window' }), // 부모 제목 상속 조각
  ];
  const r = buildAnchorMap(md(lines), s);
  assert.equal(r.anchors[1], lineOff(lines, 0));
  assert.equal(r.anchors[2], undefined); // window = 점프 비활성
  assert.equal(r.total, 1);
});

test('코드펜스 내부 heading 제외', () => {
  const lines = ['```', '# General', '```', '# General', 'x'];
  const s = [sec({ chunk_id: 1, section_title: 'General' })];
  const r = buildAnchorMap(md(lines), s);
  assert.equal(r.anchors[1], lineOff(lines, 3)); // 펜스 밖
});

test('miss = anchor 없음 (점프 비활성, 오점프 아님)', () => {
  const lines = ['# 개요', '본문'];
  const s = [
    sec({ chunk_id: 1, section_title: '개요' }),
    sec({ chunk_id: 2, section_title: '존재하지 않는 절' }),
  ];
  const r = buildAnchorMap(md(lines), s);
  assert.equal(r.anchors[1], lineOff(lines, 0));
  assert.equal(r.anchors[2], undefined);
  assert.equal(r.total, 2);
  assert.equal(r.matched, 1);
});

test('heading_path 마지막 세그먼트 fallback', () => {
  const lines = ['# 도입', 'x'];
  const s = [sec({ chunk_id: 1, section_title: null, heading_path: 'A > 도입' })];
  const r = buildAnchorMap(md(lines), s);
  assert.equal(r.anchors[1], lineOff(lines, 0));
});

test('빈 입력 안전', () => {
  assert.deepEqual(buildAnchorMap('', [sec({ section_title: 'x' })]).anchors, {});
  assert.deepEqual(buildAnchorMap('# x', []).anchors, {});
  assert.deepEqual(buildAnchorMap(null, null).anchors, {});
});
