import { describe, it, expect } from 'vitest';
import { ThinkSplitter } from '../think-splitter.js';

/** Feed deltas through a splitter and concatenate the reasoning/text outputs. */
function run(deltas: string[]): { reasoning: string; text: string } {
  const s = new ThinkSplitter();
  let reasoning = '';
  let text = '';
  for (const d of deltas) {
    const r = s.push(d);
    reasoning += r.reasoning;
    text += r.text;
  }
  const tail = s.flush();
  reasoning += tail.reasoning;
  text += tail.text;
  return { reasoning, text };
}

describe('ThinkSplitter', () => {
  it('passes through ordinary content unchanged (no arm)', () => {
    expect(run(['Hello ', 'world'])).toEqual({ reasoning: '', text: 'Hello world' });
  });

  it('splits a whole-buffer think block', () => {
    expect(run(['<think>reasoning here</think>answer'])).toEqual({
      reasoning: 'reasoning here',
      text: 'answer',
    });
  });

  it('discards whitespace before <think> and after </think>', () => {
    expect(run(['  <think>r</think>\n\nanswer'])).toEqual({ reasoning: 'r', text: 'answer' });
  });

  it('handles the open tag split across deltas', () => {
    expect(run(['<thi', 'nk>cot</think>out'])).toEqual({ reasoning: 'cot', text: 'out' });
  });

  it('handles the close tag split across deltas (the core reason this exists)', () => {
    expect(run(['<think>abc</thi', 'nk>tail'])).toEqual({ reasoning: 'abc', text: 'tail' });
  });

  it('does not hijack content that only mentions the tag mid-stream', () => {
    expect(run(['use the ', '<think> tag'])).toEqual({ reasoning: '', text: 'use the <think> tag' });
  });

  it('emits reasoning incrementally while inside, holding partial close tags', () => {
    const s = new ThinkSplitter();
    expect(s.push('<think>aaa')).toEqual({ reasoning: 'aaa', text: '' });
    // a lone "</" could be the start of the closer, so it is held back
    expect(s.push('bbb</')).toEqual({ reasoning: 'bbb', text: '' });
    expect(s.push('think>done')).toEqual({ reasoning: '', text: 'done' });
  });

  it('treats an unterminated think block at stream end as reasoning', () => {
    expect(run(['<think>never closed'])).toEqual({ reasoning: 'never closed', text: '' });
  });

  it('passes through a tag-like opener that is not <think>', () => {
    expect(run(['<div>hello</div>'])).toEqual({ reasoning: '', text: '<div>hello</div>' });
  });
});
