import * as vscode from 'vscode';
import { MIN_SELECTION_LENGTH } from '@lib/actions';
import { callProvider } from '@lib/providers';
import { readSettings, resolveConfig } from './config';
import { gatherCodeContext } from './codeContext';
import type { SuggestionSession } from './session';
import { withLexAIProgress } from './statusBar';
import { openSuggestionZone } from './suggestionZone';

const CODE_ASSIST_SYSTEM = [
  'You are LexAI Code Assist, a senior engineer working inside the user\'s IDE.',
  'The user highlighted code and described what they want. Follow their instruction precisely.',
  'You are given the selection plus workspace context (surrounding code, imports, and symbol definitions from other files when available).',
  'Use that context to resolve references — do not invent APIs that contradict the provided definitions.',
  'If the instruction asks to change code: return ONLY the replacement code for the selection (no markdown fences, no preamble).',
  'If the instruction asks to explain, review, or answer a question: return a clear explanation; use short code citations when helpful.',
  'If context is incomplete, say what is missing instead of guessing.',
].join(' ');

interface PendingCodeAssist {
  documentUri: vscode.Uri;
  range: vscode.Range;
  originalText: string;
}

let promptController: vscode.CommentController | undefined;
let promptThread: vscode.CommentThread | undefined;
let pending: PendingCodeAssist | undefined;
let extContext: vscode.ExtensionContext | undefined;

export function registerCodeAssistUi(context: vscode.ExtensionContext): void {
  extContext = context;
  promptController = vscode.comments.createCommentController('lexaiAssist', 'LexAI Code Assist');
  promptController.options = {
    placeHolder: 'Ask LexAI to explain, refactor, or fix the selection…',
    prompt: 'Code Assist',
  };
  // No free-form commenting ranges — we only open our own prompt thread.
  promptController.commentingRangeProvider = {
    provideCommentingRanges: () => [],
  };
  context.subscriptions.push(promptController);

  context.subscriptions.push(
    vscode.commands.registerCommand('lexai.codeAssist.submit', (reply?: vscode.CommentReply) =>
      void submitPrompt(reply),
    ),
    vscode.commands.registerCommand('lexai.codeAssist.cancel', () => cancelPrompt()),
  );
}

/**
 * Start Code Assist. Without a preset instruction, opens an in-editor prompt
 * (comment zone + reply textbox) above the selection — not the top InputBox.
 */
export async function runCodeAssist(
  context: vscode.ExtensionContext,
  presetInstruction?: string,
): Promise<void> {
  extContext = context;
  if (!promptController) registerCodeAssistUi(context);

  const editor = vscode.window.activeTextEditor;
  if (!editor) {
    void vscode.window.showErrorMessage('LexAI: open a text editor and select code first.');
    return;
  }

  const selection = editor.selection;
  if (selection.isEmpty) {
    void vscode.window.showErrorMessage('LexAI: select some code first.');
    return;
  }

  const selectedText = editor.document.getText(selection);
  if (selectedText.trim().length < MIN_SELECTION_LENGTH) {
    void vscode.window.showErrorMessage(
      `LexAI: select at least ${MIN_SELECTION_LENGTH} characters.`,
    );
    return;
  }

  // Snapshot before UI focus moves away from the editor.
  const snap: PendingCodeAssist = {
    documentUri: editor.document.uri,
    range: new vscode.Range(selection.start, selection.end),
    originalText: selectedText,
  };

  const goal = presetInstruction?.trim() ?? '';
  if (goal) {
    await executeCodeAssist(context, snap, goal);
    return;
  }

  await openInlinePrompt(snap);
}

async function openInlinePrompt(snap: PendingCodeAssist): Promise<void> {
  if (!promptController) return;
  cancelPrompt();
  pending = snap;

  const doc =
    vscode.workspace.textDocuments.find((d) => d.uri.toString() === snap.documentUri.toString()) ??
    (await vscode.workspace.openTextDocument(snap.documentUri));

  const startLine = snap.range.start.line;
  let anchor: vscode.Range;
  if (startLine > 0) {
    const prev = startLine - 1;
    const col = doc.lineAt(prev).text.length;
    anchor = new vscode.Range(prev, col, prev, col);
  } else {
    anchor = new vscode.Range(0, 0, 0, 0);
  }

  const md = new vscode.MarkdownString(undefined, true);
  md.isTrusted = true;
  md.appendMarkdown('**What should LexAI do with this selection?**\n\n');
  md.appendMarkdown(
    'Type in the box below, then click **Ask LexAI** (or use the thread action).\n\n',
  );
  md.appendMarkdown(
    '_Examples: Explain how this uses AuthService · Add error handling · Refactor to async_\n\n',
  );
  md.appendMarkdown(`[Cancel](command:lexai.codeAssist.cancel)`);

  const header: vscode.Comment = {
    body: md,
    mode: vscode.CommentMode.Preview,
    author: { name: 'LexAI' },
    label: 'code assist',
  };

  promptThread = promptController.createCommentThread(snap.documentUri, anchor, [header]);
  promptThread.label = 'LexAI Code Assist';
  promptThread.contextValue = 'lexaiCodeAssistPrompt';
  promptThread.collapsibleState = vscode.CommentThreadCollapsibleState.Expanded;
  promptThread.canReply = true;

  // Best-effort: put keyboard focus in the comment reply box (Copilot-like).
  setTimeout(() => {
    void vscode.commands.executeCommand('workbench.action.focusCommentsInput');
  }, 50);
}

async function submitPrompt(reply?: vscode.CommentReply): Promise<void> {
  const text = (reply?.text ?? '').trim();
  const snap = pending;
  if (!snap) {
    void vscode.window.showErrorMessage('LexAI: no Code Assist selection — select code and try again.');
    cancelPrompt();
    return;
  }
  if (text.length < 3) {
    void vscode.window.showErrorMessage('LexAI: describe what you want (a few words is enough).');
    return;
  }

  cancelPrompt();
  const ctx = extContext;
  if (!ctx) return;
  await executeCodeAssist(ctx, snap, text);
}

function cancelPrompt(): void {
  promptThread?.dispose();
  promptThread = undefined;
  pending = undefined;
}

async function executeCodeAssist(
  context: vscode.ExtensionContext,
  snap: PendingCodeAssist,
  goal: string,
): Promise<void> {
  const resolved = await resolveConfig(context);
  if (resolved.error || !resolved.config) {
    void vscode.window.showErrorMessage(`LexAI: ${resolved.error ?? 'configuration error'}`);
    return;
  }

  const doc =
    vscode.workspace.textDocuments.find((d) => d.uri.toString() === snap.documentUri.toString()) ??
    (await vscode.workspace.openTextDocument(snap.documentUri));

  const bundle = await withLexAIProgress('LexAI: gathering workspace context…', () =>
    gatherCodeContext(doc, snap.range),
  );

  const userPayload = [
    `### User instruction`,
    goal,
    '',
    bundle.contextText,
  ].join('\n');

  const response = await withLexAIProgress('LexAI: Code Assist…', () =>
    callProvider(resolved.config!, userPayload, CODE_ASSIST_SYSTEM, {
      maxTokens: Math.max(2048, Math.min(8192, Math.ceil(snap.originalText.length + 1500))),
    }),
  );

  if (response.error || !response.result) {
    void vscode.window.showErrorMessage(
      `LexAI: ${response.error ?? 'Empty response from provider.'}`,
    );
    return;
  }

  const settings = readSettings();
  const session: SuggestionSession = {
    action: 'codeAssist',
    documentUri: snap.documentUri,
    range: snap.range,
    originalText: snap.originalText,
    suggestion: stripCodeFences(response.result),
    options: {
      writingStyle: settings.writingStyle,
      promptPattern: settings.promptPattern,
      promptPersona: settings.promptPersona,
      customPersona: settings.customPersona,
      promptFormat: settings.promptFormat,
      promptModel: settings.promptModel,
    },
    instruction: goal,
    contextSummary: bundle.summary,
    contextText: bundle.contextText,
  };

  void vscode.window.showInformationMessage(`LexAI Code Assist: ${bundle.summary}`);
  await openSuggestionZone(context, session);
}

function stripCodeFences(text: string): string {
  const t = text.replace(/\r\n/g, '\n').trim();
  const m = t.match(/^```(?:[\w.+-]+)?\n([\s\S]*?)\n```$/);
  return m ? m[1] : t;
}
