import * as vscode from 'vscode';
import { getActiveBrainProfile } from '../../utils';
import type { ChatMessage } from '../../agent';

/**
 * Chat 세션 영구 저장 layer — `chat_sessions` globalState key 에 대한 CRUD 와
 * 정규화(corrupt/legacy raw → 검증된 ChatSession) 를 한 곳에 모음.
 *
 * 옛 코드: sidebarProvider 의 3개 메서드 `_getSessions`, `_getSessionById`,
 * `_putSessions` 로 흩어져 있었고 normalize/validate 로직이 두 read 메서드에
 * 거의 같게 복붙돼 있었음 — 한 쪽만 수정하면 다른 쪽에서 버그가 새는 위험.
 *
 * Manager 로 격리해 (a) 정규화 로직 단일화, (b) 50개 cap 같은 정책 한 곳, (c)
 * "이 데이터가 어디서 왔는가" 가 import 만 봐도 명확.
 *
 * 한계 — *오직* `chat_sessions` key 만 책임. activeSessionStateKey /
 * blankChatStateKey / lastVisibleChatStateKey 는 여전히 provider 가 관리 (각각
 * UI flow / restore 와 강하게 엮여 있어 함께 옮기면 deps 가 폭증).
 */

export interface ChatSession {
    id: string;
    title: string;
    timestamp: number;
    history: ChatMessage[];
    brainProfileId: string;
    negativePrompt?: string;
}

/** chat_sessions globalState 키 — provider 와 store 가 동의해야 하는 단일 source of truth. */
const SESSIONS_KEY = 'chat_sessions';

/** 영구 저장에 보관할 최대 세션 수. 오래된 것부터 잘려나간다. */
const MAX_SESSIONS = 50;

export class ChatSessionStore {
    constructor(private readonly context: vscode.ExtensionContext) {}

    /**
     * 모든 세션을 timestamp 내림차순 정렬해 반환. 깨진/legacy entry 는 normalize
     * 단계에서 떨궈진다 (history 0개 이거나 id 없는 것 등). 최대 MAX_SESSIONS 개로
     * cap.
     */
    getAll(): ChatSession[] {
        const raw = this.readRaw();
        return raw
            .map((session, index) => this.normalize(session, index))
            .filter((session): session is ChatSession => !!session)
            .sort((a, b) => b.timestamp - a.timestamp)
            .slice(0, MAX_SESSIONS);
    }

    /**
     * 특정 id 의 세션 조회. 정상 normalize 통과한 것만 반환 — 깨졌으면 null.
     * `getAll().find(...)` 와 같은 효과지만 raw 1개만 처리하므로 가볍다.
     */
    getById(id: string): ChatSession | null {
        const raw = this.readRaw();
        const found = raw.find((session: any) => String(session?.id) === String(id));
        if (!found) return null;
        return this.normalize(found, 0);
    }

    /**
     * 전체 세션 배열 덮어쓰기. 호출자는 미리 정렬해 넘기든 말든 무관 — store 는
     * 50개 cap 만 강제. *덮어쓰기* 이므로 부분 갱신은 호출자가 read → modify → write
     * 패턴으로 해야 함.
     */
    async putAll(sessions: ChatSession[]): Promise<void> {
        await this.context.globalState.update(SESSIONS_KEY, sessions.slice(0, MAX_SESSIONS));
    }

    /** Raw globalState 읽기. 항상 배열 보장. */
    private readRaw(): any[] {
        return this.context.globalState.get<any[]>(SESSIONS_KEY, []) || [];
    }

    /**
     * Raw session entry → 검증된 ChatSession 또는 null. 검증 규칙:
     *   - id 가 falsy 면 reject
     *   - history 가 배열 아니면 빈 배열로 강제
     *   - history 안의 message 는 (user/assistant/system) + content !== undefined 만 통과
     *   - 검증 후 history 0개면 reject (빈 세션은 표시 가치 0)
     * Title 이 없거나 비면 첫 user 메시지에서 50자 fallback (없으면 `Chat <index>`).
     */
    private normalize(raw: any, index: number): ChatSession | null {
        const history: ChatMessage[] = Array.isArray(raw?.history)
            ? raw.history.filter((message: any) =>
                message
                && (message.role === 'user' || message.role === 'assistant' || message.role === 'system')
                && message.content !== undefined)
            : [];

        if (!raw?.id || history.length === 0) return null;

        const firstMsg = history.find((message) => message.role === 'user')?.content;
        const fallbackTitle = typeof firstMsg === 'string'
            ? firstMsg.substring(0, 50).replace(/\n/g, ' ') + (firstMsg.length > 50 ? '...' : '')
            : `Chat ${index + 1}`;

        return {
            id: String(raw.id),
            title: String(raw.title || fallbackTitle),
            timestamp: typeof raw.timestamp === 'number' ? raw.timestamp : Date.now(),
            history,
            brainProfileId: String(raw.brainProfileId || getActiveBrainProfile().id),
            negativePrompt: String(raw.negativePrompt || ''),
        };
    }
}
