import { WORKSPACE_STORAGE_TYPE } from '@learnitive/constants';
import { LOCAL_AI_MODELS } from '@learnitive/project-ai';
import { projectFilePath } from '@learnitive/project-storage';
import { createTestEnvironment } from '@learnitive/test-utils';
import { describe, expect, it } from 'vitest';

const WS_NAME = 'ai-snippet-project';
const MAIN_TEX = `${WS_NAME}:main.tex`;

async function setup() {
  const env = createTestEnvironment();
  const services = env.instantiateAll();
  await env.mountAll();
  await services.workspaceOps.createWorkspaceInfo({
    name: WS_NAME,
    type: WORKSPACE_STORAGE_TYPE.Memory,
    metadata: {},
  });
  const source = '\\documentclass{article}\n\\begin{document}\nBefore after\n\\end{document}\n';
  await services.fileSystem.createTextFile(MAIN_TEX, source);
  await services.latexSource.load(MAIN_TEX);
  services.projectAi.configure({
    enabled: true,
    provider: 'local',
    localModelId: LOCAL_AI_MODELS[0]!.id,
    model: '',
    rememberApiKey: false,
    privacyMode: true,
    autoExplainErrors: false,
  });
  return { env, services, source };
}

describe('ProjectAiService LaTeX snippets', () => {
  it('treats an explicit table insert as authorization and mutates at the active cursor', async () => {
    const { env, services, source } = await setup();
    const offset = source.indexOf(' after');
    services.projectAi.setEditorContext(WS_NAME, {
      path: projectFilePath('main.tex'),
      line: 3,
      column: 7,
      offset,
    });

    await services.projectAi.send(WS_NAME, 'insert a dummy table at cursor');

    const state = services.projectAi.getWorkspaceState(WS_NAME);
    const response = state.messages.at(-1);
    const inserted = services.latexSource.getSnapshot(MAIN_TEX)?.text ?? '';
    expect(response?.role).toBe('assistant');
    expect(response?.snippet).toBeUndefined();
    expect(response?.content).toContain('Inserted a table into main.tex');
    expect(inserted).toContain('\\begin{table}[htbp]');
    expect(inserted.indexOf('\\begin{table}[htbp]')).toBe(offset + 1);
    expect(state.proposal).toBeUndefined();
    expect(services.latexSource.getRevealRequest(MAIN_TEX)?.select).toBe(true);
    env.controller.abort();
  });

  it('keeps create/generate equation requests preview-only', async () => {
    const { env, services, source } = await setup();

    await services.projectAi.send(WS_NAME, 'create an equation in LaTeX');

    const response = services.projectAi.getWorkspaceState(WS_NAME).messages.at(-1);
    expect(response?.snippet).toMatchObject({ kind: 'latex', category: 'equation' });
    expect(response?.snippet?.latex).toContain('\\begin{equation}');
    expect(services.latexSource.getSnapshot(MAIN_TEX)?.text).toBe(source);
    env.controller.abort();
  });

  it('inserts before end document when no editor cursor exists', async () => {
    const { env, services } = await setup();

    await services.projectAi.send(WS_NAME, 'insert an equation');

    const inserted = services.latexSource.getSnapshot(MAIN_TEX)?.text ?? '';
    const equation = inserted.indexOf('\\begin{equation}');
    const documentEnd = inserted.indexOf('\\end{document}');
    expect(equation).toBeGreaterThan(0);
    expect(equation).toBeLessThan(documentEnd);
    expect(services.projectAi.getWorkspaceState(WS_NAME).messages.at(-1)?.content).toContain('Inserted an equation into main.tex');
    env.controller.abort();
  });

  it('consumes internal tool JSON from a small model instead of exposing it', async () => {
    const { env, services } = await setup();
    const internal = services.projectAi as unknown as {
      complete(messages: readonly { role: string; content: string }[], signal: AbortSignal): Promise<string>;
    };
    internal.complete = async () => JSON.stringify({
      message: 'Inserting the requested equation.',
      tool_calls: [{
        id: 'eq-1',
        function: {
          name: 'insertEquation',
          arguments: JSON.stringify({ latexCode: '\\[a^2+b^2=c^2\\]' }),
        },
      }],
    });

    await services.projectAi.send(WS_NAME, 'insert an equation for Pythagoras');

    const state = services.projectAi.getWorkspaceState(WS_NAME);
    const response = state.messages.at(-1);
    expect(response?.content).toBe('Inserted an equation into main.tex.');
    expect(response?.content).not.toContain('toolCalls');
    expect(services.latexSource.getSnapshot(MAIN_TEX)?.text).toContain('\\[a^2+b^2=c^2\\]');
    env.controller.abort();
  });

  it('repairs common equation noun typos for explicit insert commands', async () => {
    const { env, services } = await setup();

    await services.projectAi.send(WS_NAME, 'insert an euqation');

    const inserted = services.latexSource.getSnapshot(MAIN_TEX)?.text ?? '';
    expect(inserted).toContain('\\begin{equation}');
    expect(services.projectAi.getWorkspaceState(WS_NAME).messages.at(-1)?.content).not.toContain('json');
    env.controller.abort();
  });

  it('replaces the active selection using the current editor context', async () => {
    const { env, services, source } = await setup();
    const from = source.indexOf('Before');
    const to = from + 'Before'.length;
    services.projectAi.setEditorContext(WS_NAME, {
      path: projectFilePath('main.tex'),
      line: 3,
      column: 1,
      offset: to,
      selection: 'Before',
      selectionFrom: from,
      selectionTo: to,
    });

    await services.projectAi.insertLatexSnippetAtCurrentEditor(
      WS_NAME,
      '\\[\nE = mc^2\n\\]',
    );

    const inserted = services.latexSource.getSnapshot(MAIN_TEX)?.text ?? '';
    expect(inserted).not.toContain('Before');
    expect(inserted).toContain('\\[\nE = mc^2\n\\]');
    env.controller.abort();
  });
});

it('BF4.3 previews an abstract as a formatted LaTeX snippet instead of an agent JSON mutation', async () => {
  const { env, services, source } = await setup();
  const internal = services.projectAi as unknown as {
    complete(messages: readonly { role: string; content: string }[], signal: AbortSignal): Promise<string>;
  };
  internal.complete = async () => '\\begin{abstract}\nModern GPU computing combines massive parallelism with specialized acceleration for scientific and AI workloads.\n\\end{abstract}';

  await services.projectAi.send(WS_NAME, 'write an abstract on modern GPU computing');

  const response = services.projectAi.getWorkspaceState(WS_NAME).messages.at(-1);
  expect(response?.snippet).toMatchObject({ kind: 'latex', category: 'abstract' });
  expect(response?.snippet?.latex).toContain('\\begin{abstract}');
  expect(response?.content).not.toContain('toolCalls');
  expect(services.latexSource.getSnapshot(MAIN_TEX)?.text).toBe(source);
  env.controller.abort();
});
