import * as vscode from 'vscode';
import { logInfo } from '../../utils';
import { getBridgeBaseUrl } from './bridgeClient';
import { bridgeErrorRemedy } from './llm';

/**
 * Datacollect "라디오" slash 명령 라우터.
 *
 * 사용자가 Astra 채팅에서 `/research <주제>` 같은 입력을 보내면 chatHandlers에서
 * 이 모듈로 위임. 새 UI 버튼 없이 채팅 단일 경로만으로 Datacollect bridge의
 * 무거운 기능(NotebookLM Deep Research, Playwright 웹 벤치마크, YouTube 4-렌즈
 * 분석, Blog Pipeline)을 호출한다.
 *
 * 진행 상황과 최종 결과는 모두 webview에 `streamChunk` 메시지로 흘려, 일반
 * LLM 응답과 같은 자리에 자연스럽게 표시된다.
 *
 * 명령이 처리되면 true 반환 → chatHandlers가 일반 LLM 흐름으로 안 내려가게.
 */

export interface Webview {
    postMessage(msg: any): Thenable<boolean> | boolean;
}

/**
 * Slash 명령 핸들러 시그니처. 새 명령 추가 시 이 모양으로 함수 작성 후
 * `registerSlashCommand({ name, handler, description })` 한 번 호출하면 끝.
 *
 * handler 는 처리 결과를 boolean 으로 돌려준다 — true 면 명령 처리 성공으로 간주,
 * false 면 (지금은 의미 없지만) 실패. 이 모듈의 `handleSlashCommand` 가 어떤
 * boolean 이든 *true 로* 호출자에게 반환해서 LLM fallback 으로 안 흘림.
 */
export type SlashCommandHandler = (
    arg: string,
    view: Webview | undefined,
    context?: vscode.ExtensionContext,
) => Promise<boolean>;

export interface SlashCommandDef {
    /** `/research` 처럼 슬래시 포함. case-insensitive 비교. */
    name: string;
    /** 짧은 한 줄 설명 — /help 같은 향후 명령에서 활용 */
    description?: string;
    handler: SlashCommandHandler;
}

/**
 * 등록된 명령 맵 — name (소문자) → SlashCommandDef.
 *
 * 이전엔 `case '/research': ...` 식으로 case 분기가 박혀 있어서 새 명령 추가 시
 * (1) COMMANDS 배열 수정, (2) handleSlashCommand 의 switch 케이스 추가, (3) 함수
 * 구현, (4) Webview 메시지 흐름 보강 4단계가 필요. 레지스트리 패턴으로 (3) 만
 * 작성하고 register 한 번 호출하면 (1)(2)(4) 자동 처리.
 */
const REGISTRY = new Map<string, SlashCommandDef>();

/**
 * 새 슬래시 명령 등록. 같은 이름이 이미 있으면 덮어쓴다 (외부 플러그인의
 * 임의 교체 허용 — Astra 코어 명령을 사용자가 own handler 로 바꾸고 싶을 때).
 */
export function registerSlashCommand(def: SlashCommandDef): void {
    REGISTRY.set(def.name.toLowerCase(), def);
}

/** 디버그·내부 용 — 현재 등록된 명령 목록 (불변 복사본). */
export function listSlashCommands(): ReadonlyArray<SlashCommandDef> {
    return Array.from(REGISTRY.values());
}

/**
 * 최근 실행된 슬래시 명령 ring buffer (in-memory, process-scoped).
 *
 * 용도: Actionability scoring (`src/retrieval/actionabilityScoring.ts`) — 사용자가
 * 막 실행한 명령의 키워드를 검색 결과 재가중에 활용. 예: `/runway` 직후 chat 입력
 * 하면 runway/재무 관련 문서가 같은 키워드 매치 점수여도 위로 정렬.
 *
 * Persistence: 의도적으로 in-memory. 세션 사이 보존은 불필요 — actionability 는
 * "지금 작업 중인 컨텍스트" 신호이므로 process restart 시 reset 자연스러움.
 */
const RECENT_SLASH_RING: string[] = [];
const RECENT_SLASH_RING_MAX = 10;

export function getRecentSlashCommands(): string[] {
    return RECENT_SLASH_RING.slice();
}

function pushRecentSlashCommand(name: string): void {
    // 중복 제거 — 같은 명령 연속 실행해도 ring 에는 한 번만 (최신 위치로 이동)
    const idx = RECENT_SLASH_RING.indexOf(name);
    if (idx >= 0) RECENT_SLASH_RING.splice(idx, 1);
    RECENT_SLASH_RING.unshift(name);
    if (RECENT_SLASH_RING.length > RECENT_SLASH_RING_MAX) RECENT_SLASH_RING.length = RECENT_SLASH_RING_MAX;
}

export function isSlashCommand(input: string): boolean {
    const trimmed = input.trim();
    if (!trimmed.startsWith('/')) return false;
    const head = trimmed.split(/\s+/, 1)[0].toLowerCase();
    return REGISTRY.has(head);
}

export function chunk(view: Webview | undefined, value: string) {
    view?.postMessage({ type: 'streamChunk', value });
}

/**
 * 슬래시 명령을 라우팅. chatHandlers에서 `userPrompt.startsWith('/')` 체크 후 호출.
 * 처리 성공/실패 모두 true 반환 (사용자가 명령을 의도했음을 명확히 표현했으므로
 * LLM fallback로 흘리지 않음).
 *
 * **반드시 finally에서 `streamEnd`를 보낸다** — Astra webview의 채팅 input은
 * `streamEnd` 메시지를 받아야 잠금이 풀린다(sidebarProvider.ts 참조). 일반 LLM
 * 흐름은 streamer가 자동으로 보내지만, 우리는 LLM을 우회해 bridge를 직접 호출
 * 하므로 명시적으로 보내야 한다. 안 보내면 timeout/에러/성공 어떤 경우에도
 * input이 영원히 잠긴 채 사용자가 무한 로딩 상태로 보게 됨.
 */
export async function handleSlashCommand(
    input: string,
    view: Webview | undefined,
    context?: vscode.ExtensionContext,
): Promise<boolean> {
    const trimmed = input.trim();
    const spaceIdx = trimmed.indexOf(' ');
    const head = (spaceIdx === -1 ? trimmed : trimmed.slice(0, spaceIdx)).toLowerCase();
    const arg = spaceIdx === -1 ? '' : trimmed.slice(spaceIdx + 1).trim();

    const def = REGISTRY.get(head);
    if (!def) {
        // 호출자가 isSlashCommand 로 사전 필터링하니 보통 도달 안 함. 방어 코드.
        return false;
    }

    // Actionability ring — 명령 이름을 ring buffer 에 push. 다음 일반 채팅 turn 의
    // 검색 재가중에 사용 (src/retrieval/actionabilityScoring.ts).
    pushRecentSlashCommand(head);

    logInfo(`[ASTRA-DEBUG] slashRouter handleSlashCommand head=${head} arg=${arg.slice(0, 40)}`);
    logInfo(`[SLASH] handleSlashCommand start head=${head} arg="${arg.slice(0, 60)}" bridge=${getBridgeBaseUrl()}`);
    void vscode.window.showInformationMessage(`Datacollect Radio: ${head} 진입`);
    void vscode.window.setStatusBarMessage(`Datacollect Radio: ${head} 처리 중…`, 5000);
    // streamEnd(finally)와 대칭 — webview는 streamStart를 받아야 assistant 메시지
    // 버블(streamBody)을 만든다(sidebar.js 참조). 이게 없으면 이후 chunk가 보내는
    // streamChunk가 streamBody 부재로 전부 silently drop돼 사용자는 아무 결과도
    // 못 본다. 일반 LLM 경로는 streamer가 streamStart를 보내주지만, slash 경로는
    // LLM을 우회하므로 streamEnd처럼 streamStart도 명시적으로 보내야 한다.
    view?.postMessage({ type: 'streamStart' });
    chunk(view, `\n\n**Datacollect Radio** · \`${head}\` · bridge=\`${getBridgeBaseUrl()}\`\n\n`);

    try {
        return await def.handler(arg, view, context);
    } catch (e: any) {
        const errMsg = e?.message || String(e);
        logInfo(`[SLASH] handleSlashCommand error head=${head}: ${errMsg}`);
        chunk(view, `\n\n> ❌ **에러**: ${errMsg}\n`);
        // 자주 발생하는 환경 의존성 에러는 사용자가 즉시 해결할 수 있게 명령 가이드 자동 첨부.
        const remedy = bridgeErrorRemedy(errMsg);
        if (remedy) chunk(view, remedy);
        // Python 패키지 미설치 패턴이면 한 클릭 설치 notification 도 같이 띄움.
        // 채팅 텍스트만 보면 사용자가 명령 팔레트로 가기 귀찮으니까 actionable 버튼 제공.
        const pkgMatch = errMsg.match(/필수 패키지가 없습니다?[:\s]+([\w\-,\s.]+)/i)
            || errMsg.match(/missing (?:python )?packages?[:\s]+([\w\-,\s.]+)/i);
        if (pkgMatch) {
            try {
                const { offerInstallNotification } = await import('../setup/datacollectSetup');
                void offerInstallNotification(pkgMatch[1].trim());
            } catch { /* setup 모듈 로드 실패해도 텍스트 가이드는 이미 보냈으니 무시 */ }
        }
        return true;
    } finally {
        // input 잠금 해제 — slashRouter 진입했으면 어떤 경로든 반드시 통과.
        view?.postMessage({ type: 'streamEnd' });
        logInfo(`[SLASH] handleSlashCommand finished head=${head} streamEnd posted`);
    }
}


// ─── /task — 단발 작업을 Google Tasks + Calendar 양쪽에 등록 ─────────────
// `/task <제목> <시작일> <완료일>` (range) 또는 `/task <제목> <날짜>` (단일일)
// 날짜 형식: YY/MM/DD (예: 26/05/27) 또는 YYYY-MM-DD / YYYY/MM/DD.
// /meet 와 달리 사용자가 직접 호출한 명령이므로 *항상* 양쪽 등록(설정 무시).

/** 유연한 한국 날짜 파서. YY/MM/DD · YYYY/MM/DD · YYYY-MM-DD 지원. 잘못된 입력은 null. */



// ─── 기본 명령 등록 ───────────────────────────────────────────────────────
// Astra 가 기본 제공하는 6개 명령. 외부 플러그인 / 사용자 정의 명령은 동일하게
// `registerSlashCommand({ name, description, handler })` 한 번 호출로 추가.
//
// 새 명령 추가 패턴:
//   async function runMyCmd(arg, view, context) { ... return true; }
//   registerSlashCommand({ name: '/my', description: '...', handler: runMyCmd });
//
// 이 한 줄이면 isSlashCommand(input) 와 handleSlashCommand 가 자동으로 인식.

// /stocks 는 `src/features/stocks/slashStocks.ts` 의 sub-command 라우터로 위임.
// list/check/signal/sync/add/remove/judge/report/run/path 9 개의 subcommand 가 그 안에서 분기.
import { handleStocksCommand } from '../stocks';
registerSlashCommand({ name: '/stocks', description: '종목 모니터링 — Yahoo 가격, Sheets 동기화, 텔레그램 보고서, LLM judge', handler: handleStocksCommand });

// TeamOps handlers — v2.2.196 부터 도메인별 파일로 분리. 배럴 import 는 entry point
// (`src/extension.ts`) 에서 처리 — 순환 import 피하기 위함.
