import * as vscode from 'vscode';
import {
  PROMPT_FORMATS,
  PROMPT_PATTERNS,
  PROMPT_PERSONAS,
  WRITING_STYLES,
  resolvePromptPersona,
  type ActionId,
} from '@lib/actions';
import { callProvider } from '@lib/providers';
import { readSettings, resolveConfig, updateSettings } from './config';
import { generateSuggestion } from './llm';
import {
  actionLabel,
  applySuggestionReplace,
  type SuggestionOptions,
  type SuggestionSession,
} from './session';
import { withLexAIProgress } from './statusBar';

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(' ');

let controller: vscode.CommentController | undefined;
let thread: vscode.CommentThread | undefined;
let session: SuggestionSession | undefined;
let extContext: vscode.ExtensionContext | undefined;

type OptionKey = 'writingStyle' | 'promptPattern' | 'promptPersona' | 'promptFormat';

export function registerSuggestionZone(context: vscode.ExtensionContext): void {
  extContext = context;
  controller = vscode.comments.createCommentController('lexai', 'LexAI');
  controller.options = {
    placeHolder: 'LexAI suggestion',
    prompt: 'Review the suggestion, then Accept or Regenerate.',
  };
  controller.commentingRangeProvider = {
    provideCommentingRanges: () => [],
  };
  context.subscriptions.push(controller);

  context.subscriptions.push(
    vscode.commands.registerCommand('lexai.zone.accept', () => void accept()),
    vscode.commands.registerCommand('lexai.zone.regenerate', () => void regenerate()),
    vscode.commands.registerCommand('lexai.zone.discard', () => discard()),
    vscode.commands.registerCommand(
      'lexai.zone.setOption',
      (key: OptionKey, value: string) => void setOptionAndRegenerate(key, value),
    ),
    vscode.commands.registerCommand('lexai.zone.openPanel', async () => {
      if (!session || !extContext) return;
      const { openSuggestionPanel } = await import('./suggestionPanel');
      await openSuggestionPanel(extContext, session);
    }),
  );
}

export async function openSuggestionZone(
  context: vscode.ExtensionContext,
  next: SuggestionSession,
): Promise<void> {
  extContext = context;
  session = next;
  if (!controller) registerSuggestionZone(context);

  disposeThread();

  // Comment widgets render *below* their anchor line. Anchor on the previous
  // line so the zone appears before the first highlighted line.
  const doc =
    vscode.workspace.textDocuments.find((d) => d.uri.toString() === next.documentUri.toString()) ??
    (await vscode.workspace.openTextDocument(next.documentUri));
  const startLine = next.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);
  }

  thread = controller!.createCommentThread(next.documentUri, anchor, [
    buildComment(next),
  ]);
  thread.label = `LexAI: ${actionLabel(next.action)}`;
  thread.contextValue = 'lexaiSuggestion';
  thread.collapsibleState = vscode.CommentThreadCollapsibleState.Expanded;
  thread.canReply = false;
}

function disposeThread(): void {
  thread?.dispose();
  thread = undefined;
}

function discard(): void {
  disposeThread();
  session = undefined;
}

/** Soft-wrap long lines for readable comment display only (Accept still uses raw text). */
function wrapForDisplay(text: string, width = 72): string {
  return text
    .replace(/\r\n/g, '\n')
    .split('\n')
    .map((line) => {
      if (line.length <= width) return line;
      const words = line.split(/(\s+)/);
      const rows: string[] = [];
      let row = '';
      for (const part of words) {
        if (row.length + part.length > width && row.length > 0) {
          rows.push(row);
          row = part.trimStart();
        } else {
          row += part;
        }
      }
      if (row) rows.push(row);
      return rows.join('\n');
    })
    .join('\n');
}

function cmdLink(title: string, command: string, args: unknown[]): string {
  const encoded = encodeURIComponent(JSON.stringify(args));
  return `[${title}](command:${command}?${encoded})`;
}

function optionChip(label: string, key: OptionKey, value: string, selected: boolean): string {
  const title = selected ? `✓ ${label}` : label;
  return cmdLink(title, 'lexai.zone.setOption', [key, value]);
}

function buildOptionsRow(s: SuggestionSession): string {
  if (s.action === 'codeAssist') {
    const instr = (s.instruction || '').replace(/\n/g, ' ').slice(0, 200);
    const ctx = s.contextSummary || 'Workspace context attached.';
    return (
      `**Instruction** ${instr}\n\n` +
      `**Context** ${ctx}\n\n` +
      `_Accept replaces the selection (use for code changes). For explanations, copy from the draft instead._\n\n`
    );
  }

  if (s.action === 'prompt') {
    const patterns = PROMPT_PATTERNS.map((p) =>
      optionChip(p.label, 'promptPattern', p.id, s.options.promptPattern === p.id),
    ).join(' · ');
    // Personas / formats: keep the common ones inline; Custom still via chip + input elsewhere
    const personas = PROMPT_PERSONAS.filter((p) => p !== 'Custom…')
      .map((p) => optionChip(p, 'promptPersona', p, s.options.promptPersona === p))
      .join(' · ');
    const formats = PROMPT_FORMATS.map((p) =>
      optionChip(p, 'promptFormat', p, s.options.promptFormat === p),
    ).join(' · ');
    return (
      `**Pattern** ${patterns}\n\n` +
      `**Persona** ${personas}\n\n` +
      `**Format** ${formats}\n\n`
    );
  }

  const styles = WRITING_STYLES.map((style) =>
    optionChip(style, 'writingStyle', style, s.options.writingStyle === style),
  ).join(' · ');
  return `**Writing style** ${styles}\n\n`;
}

function buildComment(s: SuggestionSession): vscode.Comment {
  const suggestion = s.suggestion.replace(/\r\n/g, '\n');
  const display = wrapForDisplay(suggestion);
  const lineCount = display.split('\n').length;

  const md = new vscode.MarkdownString(undefined, true);
  md.isTrusted = true;
  md.supportHtml = true;

  md.appendMarkdown(
    `[Accept](command:lexai.zone.accept) · [Regenerate](command:lexai.zone.regenerate) · [Discard](command:lexai.zone.discard)\n\n`,
  );
  md.appendMarkdown(buildOptionsRow(s));
  md.appendMarkdown(`_Click an option to apply it and regenerate · ${lineCount} line${lineCount === 1 ? '' : 's'}_\n\n`);
  md.appendMarkdown('---\n\n');
  const fence = '````';
  md.appendMarkdown(`${fence}text\n${display}\n${fence}\n`);

  return {
    body: md,
    mode: vscode.CommentMode.Preview,
    author: { name: 'LexAI' },
    label: 'suggestion',
  };
}

function refreshThread(): void {
  if (!thread || !session) return;
  thread.label = `LexAI: ${actionLabel(session.action)}`;
  thread.comments = [buildComment(session)];
}

async function setOptionAndRegenerate(key: OptionKey, value: string): Promise<void> {
  if (!session) return;

  if (key === 'writingStyle') {
    session.options = {
      ...session.options,
      writingStyle: value as SuggestionOptions['writingStyle'],
    };
  } else if (key === 'promptPattern') {
    session.options = { ...session.options, promptPattern: value };
  } else if (key === 'promptPersona') {
    session.options = { ...session.options, promptPersona: value };
  } else if (key === 'promptFormat') {
    session.options = { ...session.options, promptFormat: value };
  }

  // Persist as defaults so the next run starts from the last choice.
  await updateSettings({ [key]: value });
  refreshThread();
  await regenerate();
}

async function accept(): Promise<void> {
  if (!session) return;
  const ok = await applySuggestionReplace(session, session.suggestion);
  if (ok) {
    void vscode.window.showInformationMessage(
      `LexAI: ${actionLabel(session.action)} applied.`,
    );
    discard();
  }
}

async function regenerate(): Promise<void> {
  if (!session || !extContext) return;
  const resolved = await resolveConfig(extContext);
  if (resolved.error || !resolved.config) {
    void vscode.window.showErrorMessage(`LexAI: ${resolved.error ?? 'configuration error'}`);
    return;
  }

  const label = actionLabel(session.action);

  if (session.action === 'codeAssist') {
    const goal = session.instruction || 'Improve this code.';
    const ctx = session.contextText || session.originalText;
    const userPayload = [`### User instruction`, goal, '', ctx].join('\n');
    const response = await withLexAIProgress(`LexAI: regenerating ${label}…`, () =>
      callProvider(resolved.config!, userPayload, CODE_ASSIST_SYSTEM, {
        maxTokens: Math.max(2048, Math.min(8192, Math.ceil(session!.originalText.length + 1500))),
      }),
    );
    if (response.error || !response.result) {
      void vscode.window.showErrorMessage(
        `LexAI: ${response.error ?? 'Empty response from provider.'}`,
      );
      return;
    }
    session.suggestion = response.result.replace(/\r\n/g, '\n').trim()
      .replace(/^```(?:[\w.+-]+)?\n([\s\S]*?)\n```$/, '$1');
    refreshThread();
    return;
  }

  const response = await withLexAIProgress(`LexAI: regenerating ${label}…`, () =>
    generateSuggestion({
      action: session!.action as ActionId,
      text: session!.originalText,
      config: resolved.config!,
      writingStyle: session!.options.writingStyle,
      promptParams: {
        pattern: session!.options.promptPattern,
        persona: resolvePromptPersona(
          session!.options.promptPersona,
          session!.options.customPersona,
        ),
        format: session!.options.promptFormat,
      },
      promptModel: session!.options.promptModel || undefined,
    }),
  );

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

export async function pickOptionsForAction(
  action: ActionId,
  current: SuggestionOptions,
): Promise<SuggestionOptions | undefined> {
  // Kept for askOptionsBeforeGenerate; primary UX is in-zone chips.
  if (action !== 'prompt') {
    const style = await vscode.window.showQuickPick([...WRITING_STYLES], {
      title: 'Writing style',
      placeHolder: current.writingStyle,
    });
    if (!style) return undefined;
    return { ...current, writingStyle: style as SuggestionOptions['writingStyle'] };
  }

  const patternPick = await vscode.window.showQuickPick(
    PROMPT_PATTERNS.map((p) => ({
      label: p.label,
      description: p.group,
      detail: p.hint,
      id: p.id,
    })),
    { title: 'Prompt pattern', placeHolder: current.promptPattern },
  );
  if (!patternPick) return undefined;

  const persona = await vscode.window.showQuickPick([...PROMPT_PERSONAS], {
    title: 'Persona',
    placeHolder: current.promptPersona,
  });
  if (!persona) return undefined;

  let customPersona = current.customPersona;
  if (persona === 'Custom…') {
    customPersona =
      (await vscode.window.showInputBox({
        title: 'Custom persona',
        value: current.customPersona,
      })) ?? current.customPersona;
  }

  const format = await vscode.window.showQuickPick([...PROMPT_FORMATS], {
    title: 'Output format',
    placeHolder: current.promptFormat,
  });
  if (!format) return undefined;

  return {
    ...current,
    promptPattern: patternPick.id,
    promptPersona: persona,
    customPersona,
    promptFormat: format,
  };
}

/** Optional: offer options QuickPick before the first generation. */
export async function maybeTuneOptionsBeforeRun(
  action: ActionId,
): Promise<SuggestionOptions | undefined> {
  const settings = readSettings();
  const base: SuggestionOptions = {
    writingStyle: settings.writingStyle,
    promptPattern: settings.promptPattern,
    promptPersona: settings.promptPersona,
    customPersona: settings.customPersona,
    promptFormat: settings.promptFormat,
    promptModel: settings.promptModel,
  };

  const tune = vscode.workspace
    .getConfiguration('lexai')
    .get<boolean>('askOptionsBeforeGenerate', false);
  if (!tune) return base;
  return (await pickOptionsForAction(action, base)) ?? base;
}
