using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text.Json;
using AxCopilot.Models;
using AxCopilot.Services;

namespace AxCopilot.Services.Agent;

/// <summary>
/// 에이전트 루프 엔진: LLM과 대화하며 도구/스킬을 실행하는 반복 루프.
/// 계획 → 도구 실행 → 관찰 → 재평가 패턴을 구현합니다.
/// </summary>
public partial class AgentLoopService
{
    private static App? CurrentApp => System.Windows.Application.Current as App;

    // ─── Phase 33-A: 매직 넘버 상수화 ───────────────────────────────────
    private static class Defaults
    {
        // 반복 제어
        public const int MaxIterations = 25;
        public const int MaxRetryOnError = 3;
        public const int MaxTestFixIterations = 5;
        public const int MaxPlanRegenerationRetries = 3;
        public const int MaxToolExecutionRetries = 2;
        public const int MaxPostDocPlanRetries = 2;
        public const int FreeTierDelaySeconds = 4;

        // 컨텍스트 관리
        public const int AutoCompactThresholdPercent = 80;
        public const double ContextCompressionTargetRatio = 0.6;
        public const int ToolCallThresholdForExpansion = 15;

        // 텍스트 절단 길이
        public const int QueryNameMaxLength = 40;
        public const int QueryTitleMaxLength = 60;
        public const int ThinkingTextMaxLength = 150;
        public const int VerificationSummaryMaxLength = 300;
        public const int LogSummaryMaxLength = 200;
        public const int ToolResultTruncateLength = 4000;
    }

    private readonly LlmService _llm;
    private readonly ToolRegistry _tools;
    private readonly SettingsService _settings;
    private readonly SessionManager? _sessionManager; // Phase 33-D: DI 기반

    /// <summary>에이전트 이벤트 스트림 (UI 바인딩용).</summary>
    public ObservableCollection<AgentEvent> Events { get; } = new();

    /// <summary>현재 루프 실행 중 여부.</summary>
    public bool IsRunning { get; private set; }

    /// <summary>이벤트 발생 시 UI 스레드에서 호출할 디스패처.</summary>
    public Action<Action>? Dispatcher { get; set; }

    /// <summary>Ask 모드 권한 확인 콜백. (toolName, filePath) → bool</summary>
    public Func<string, string, Task<bool>>? AskPermissionCallback { get; set; }

    /// <summary>에이전트 질문 콜백 (UserAskTool 전용). (질문, 선택지, 기본값) → 응답.</summary>
    public Func<string, List<string>, string, Task<string?>>? UserAskCallback { get; set; }

    /// <summary>현재 활성 탭 (파일명 타임스탬프 등 탭별 동작 제어용).</summary>
    public string ActiveTab { get; set; } = "Chat";

    /// <summary>현재 대화 ID (감사 로그 기록용).</summary>
    private string _conversationId = "";

    /// <summary>문서 생성 폴백 재시도 여부 (루프당 1회만).</summary>
    private bool _docFallbackAttempted;

    /// <summary>현재 세션 ID (AgentEventLog 기록용).</summary>
    private string _sessionId = "";

    /// <summary>현재 세션 이벤트 로그 (비동기 JSONL 기록).</summary>
    private AgentEventLog? _eventLog;

    /// <summary>위임 에이전트 도구 참조 (서브에이전트 실행기 주입용).</summary>
    private DelegateAgentTool? _delegateAgentTool;

    /// <summary>Phase 17-C4: watchPaths 파일 감시 서비스.</summary>
    private readonly FileWatcherService _fileWatcher = new();

    /// <summary>일시정지 제어용 세마포어. 1이면 진행, 0이면 대기.</summary>
    private readonly SemaphoreSlim _pauseSemaphore = new(1, 1);

    /// <summary>현재 일시정지 상태 여부.</summary>
    public bool IsPaused { get; private set; }

    /// <summary>
    /// 사용자 의사결정 콜백. 계획 제시 후 사용자 승인을 대기합니다.
    /// (planSummary, options) → 선택된 옵션 텍스트. null이면 승인(계속 진행).
    /// </summary>
    public Func<string, List<string>, Task<string?>>? UserDecisionCallback { get; set; }

    /// <summary>에이전트 이벤트 발생 시 호출되는 콜백 (UI 표시용).</summary>
    public event Action<AgentEvent>? EventOccurred;

    public AgentLoopService(LlmService llm, ToolRegistry tools, SettingsService settings,
        SessionManager? sessionManager = null)
    {
        _llm = llm;
        _tools = tools;
        _settings = settings;
        _sessionManager = sessionManager;

        // DelegateAgentTool에 서브에이전트 실행기 주입
        _delegateAgentTool = tools.Get("delegate") as DelegateAgentTool;
        _delegateAgentTool?.SetSubAgentRunner(RunSubAgentAsync);

        // Phase 17-D: SkillManagerTool에 fork 실행기 주입
        (tools.Get("skill_manager") as SkillManagerTool)?.SetForkRunner(RunSkillInForkAsync);
    }

    /// <summary>
    /// 에이전트 루프를 일시정지합니다.
    /// 다음 반복 시작 시점에서 대기 상태가 됩니다.
    /// </summary>
    public async Task PauseAsync()
    {
        if (IsPaused || !IsRunning) return;
        // 세마포어를 획득하여 루프가 다음 반복에서 대기하게 함
        await _pauseSemaphore.WaitAsync().ConfigureAwait(false);
        IsPaused = true;
        EmitEvent(AgentEventType.Paused, "", "에이전트가 일시정지되었습니다");
    }

    /// <summary>
    /// 일시정지된 에이전트 루프를 재개합니다.
    /// </summary>
    public void Resume()
    {
        if (!IsPaused) return;
        IsPaused = false;
        try
        {
            _pauseSemaphore.Release();
        }
        catch (SemaphoreFullException)
        {
            // 이미 릴리즈된 상태 — 무시
        }
        EmitEvent(AgentEventType.Resumed, "", "에이전트가 재개되었습니다");
    }

    /// <summary>
    /// 에이전트 루프를 실행합니다.
    /// 사용자 메시지를 LLM에 전달하고, LLM이 도구를 호출하면 실행 후 결과를 다시 LLM에 피드백합니다.
    /// LLM이 더 이상 도구를 호출하지 않으면 (텍스트만 반환) 루프를 종료합니다.
    /// </summary>
    /// <param name="messages">대화 메시지 목록 (시스템 프롬프트 포함)</param>
    /// <param name="ct">취소 토큰</param>
    /// <returns>최종 텍스트 응답</returns>
    public async Task<string> RunAsync(List<ChatMessage> messages, CancellationToken ct = default)
    {
        if (IsRunning) throw new InvalidOperationException("에이전트가 이미 실행 중입니다.");

        IsRunning = true;
        _docFallbackAttempted = false;
        _sessionId = Guid.NewGuid().ToString("N")[..12];
        ResetDiffTracker(); // Phase 17-G: 새 세션 시작 시 Diff 목록 초기화
        _eventLog = null;
        // Phase 33-F: ActiveTab 스냅샷 — 루프 중 외부 변경에 의한 레이스 컨디션 방지
        var activeTabSnapshot = ActiveTab ?? "Chat";
        var llm = _settings.Settings.Llm;
        if (llm.EventLog?.Enabled ?? true)
        {
            _eventLog = new AgentEventLog(_sessionId);
            _ = _eventLog.AppendAsync(AgentEventLogType.SessionStart,
                JsonSerializer.Serialize(new { tab = activeTabSnapshot, model = llm.Model ?? "" }));
        }

        var baseMax = llm.MaxAgentIterations > 0 ? llm.MaxAgentIterations : Defaults.MaxIterations;
        var maxIterations = baseMax;
        var maxRetry = llm.MaxRetryOnError > 0 ? llm.MaxRetryOnError : Defaults.MaxRetryOnError;
        var iteration = 0;

        // 사용자 원본 요청 캡처 (문서 생성 폴백 판단용)
        var userQuery = messages.LastOrDefault(m => m.Role == "user")?.Content ?? "";

        // Phase 17-B: UserMessage 이벤트 기록 + TaskState Working Memory 초기화
        if (!string.IsNullOrWhiteSpace(userQuery))
            _ = _eventLog?.AppendAsync(AgentEventLogType.UserMessage,
                JsonSerializer.Serialize(new { length = userQuery.Length }));
        await InitTaskStateAsync(userQuery, _sessionId);

        // Phase 17-C: SessionStart 훅 실행 (fire-and-forget)
        _ = RunExtendedEventAsync(HookEventKind.SessionStart, messages, CancellationToken.None,
            userMessage: userQuery);

        // Phase 17-C: UserPromptSubmit 훅 실행 — 차단 시 즉시 종료
        if (!string.IsNullOrWhiteSpace(userQuery))
        {
            var promptBlocked = await RunExtendedEventAsync(
                HookEventKind.UserPromptSubmit, messages, ct, userMessage: userQuery);
            if (promptBlocked)
            {
                EmitEvent(AgentEventType.Complete, "", "훅 정책에 의해 요청이 차단되었습니다");
                return "⚠ 요청이 훅 정책에 의해 차단되었습니다.";
            }
        }

        var consecutiveErrors = 0; // Self-Reflection: 연속 오류 카운터
        var totalToolCalls = 0;    // 복잡도 추정용

        // 통계 수집
        var statsStart = DateTime.Now;
        var statsSuccessCount = 0;
        var statsFailCount = 0;
        var statsInputTokens = 0;
        var statsOutputTokens = 0;
        var statsUsedTools = new List<string>();

        // Task Decomposition: 계획 단계 추적
        var planSteps = new List<string>();
        var currentStep = 0;
        var planExtracted = false;
        var planExecutionRetry = 0;    // 계획 승인 후 도구 미호출 재시도 카운터
        var documentPlanCalled = false;   // document_plan 도구 호출 여부
        var postDocumentPlanRetry = 0;    // document_plan 후 terminal 도구 미호출 재시도 카운터
        string? documentPlanPath = null;     // document_plan이 제안한 파일명
        string? documentPlanTitle = null;    // document_plan이 제안한 문서 제목
        string? documentPlanScaffold = null; // document_plan이 생성한 body 골격 HTML

        // 플랜 모드 설정
        var planMode = llm.PlanMode ?? "off"; // off | always | auto

        var context = BuildContext(activeTabSnapshot);

        // Phase 18-A1: 코디네이터 에이전트 모드 — 계획 수립 후 서브에이전트 위임
        if (llm.EnableCoordinatorMode && !string.IsNullOrWhiteSpace(userQuery))
        {
            var coordResult = await RunCoordinatorModeAsync(userQuery, llm.WorkFolder ?? "", ct);
            if (coordResult != null)
            {
                IsRunning = false;
                return coordResult;
            }
            // null 반환 시 일반 에이전트 루프로 fallback
        }

        // Phase 17-A: Reflexion — 과거 교훈을 시스템 메시지에 주입
        await InjectReflexionContextAsync(messages, userQuery);

        // Phase 17-E: 4-layer 계층 메모리 + @include 해석 → 시스템 메시지 주입
        await InjectHierarchicalMemoryAsync(messages, llm.WorkFolder, ct);

        try
        {
            // ── 플랜 모드 "always": 첫 번째 호출은 계획만 생성 (도구 없이) ──
            if (planMode == "always")
            {
                iteration++;
                EmitEvent(AgentEventType.Thinking, "", "실행 계획 생성 중...");

                // 계획 생성 전용 시스템 지시를 임시 추가
                var planInstruction = new ChatMessage
                {
                    Role = "user",
                    Content = "[System] 도구를 호출하지 마세요. 먼저 실행 계획을 번호 매긴 단계로 작성하세요. " +
                              "각 단계에 사용할 도구와 대상을 구체적으로 명시하세요. " +
                              "계획만 제시하고 실행은 하지 마세요."
                };
                messages.Add(planInstruction);

                // 도구 없이 텍스트만 요청
                string planText;
                try
                {
                    planText = await _llm.SendAsync(messages, ct);
                }
                catch (Exception ex)
                {
                    EmitEvent(AgentEventType.Error, "", $"LLM 오류: {ex.Message}");
                    return $"⚠ LLM 오류: {ex.Message}";
                }

                // 계획 지시 메시지 제거 (실제 실행 시 혼란 방지)
                messages.Remove(planInstruction);

                // 계획 추출
                planSteps = TaskDecomposer.ExtractSteps(planText);
                planExtracted = true;

                if (planSteps.Count > 0)
                {
                    EmitEvent(AgentEventType.Planning, "", $"작업 계획: {planSteps.Count}단계",
                        steps: planSteps);

                    // 사용자 승인 대기
                    if (UserDecisionCallback != null)
                    {
                        var decision = await UserDecisionCallback(
                            planText,
                            new List<string> { "승인", "수정 요청", "취소" });

                        if (decision == "취소")
                        {
                            EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다");
                            return "작업이 취소되었습니다.";
                        }
                        else if (decision != null && decision != "승인")
                        {
                            // 수정 요청 — 피드백으로 계획 재생성
                            messages.Add(new ChatMessage { Role = "assistant", Content = planText });
                            messages.Add(new ChatMessage { Role = "user", Content = decision + "\n위 피드백을 반영하여 실행 계획을 다시 작성하세요." });

                            // 재생성 루프
                            for (int retry = 0; retry < Defaults.MaxPlanRegenerationRetries; retry++)
                            {
                                try { planText = await _llm.SendAsync(messages, ct); }
                                catch (Exception ex) { LogService.Warn($"[AgentLoop] 계획 재생성 실패: {ex.Message}"); break; }

                                planSteps = TaskDecomposer.ExtractSteps(planText);
                                if (planSteps.Count > 0)
                                {
                                    EmitEvent(AgentEventType.Planning, "", $"수정된 계획: {planSteps.Count}단계",
                                        steps: planSteps);
                                }

                                decision = await UserDecisionCallback(
                                    planText,
                                    new List<string> { "승인", "수정 요청", "취소" });

                                if (decision == "취소")
                                {
                                    EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다");
                                    return "작업이 취소되었습니다.";
                                }
                                if (decision == null || decision == "승인") break;

                                // 재수정
                                messages.Add(new ChatMessage { Role = "assistant", Content = planText });
                                messages.Add(new ChatMessage { Role = "user", Content = decision + "\n위 피드백을 반영하여 실행 계획을 다시 작성하세요." });
                            }
                        }
                    }



                    // 승인된 계획을 컨텍스트에 포함하여 실행 유도
                    // 도구 호출을 명확히 강제하여 텍스트 응답만 반환하는 경우 방지
                    messages.Add(new ChatMessage { Role = "assistant", Content = planText });

                    // 1차 계획의 단계들을 document_plan의 sections_hint로 전달하도록 지시
                    // → BuildSections() 하드코딩 대신 LLM이 잡은 섹션 구조가 문서에 반영됨
                    var planSectionsHint = planSteps.Count > 0
                        ? string.Join(", ", planSteps)
                        : "";
                    var sectionInstruction = !string.IsNullOrEmpty(planSectionsHint)
                        ? $"document_plan 도구를 호출할 때 sections_hint 파라미터에 위 계획의 섹션/단계를 그대로 넣으세요: \"{planSectionsHint}\""
                        : "";

                    messages.Add(new ChatMessage { Role = "user",
                        Content = "계획이 승인되었습니다. 지금 즉시 1단계부터 도구(tool)를 호출하여 실행을 시작하세요. " +
                                  "텍스트로 설명하지 말고 반드시 도구를 호출하세요." +
                                  (string.IsNullOrEmpty(sectionInstruction) ? "" : "\n" + sectionInstruction) });
                }
                else
                {
                    // 계획 추출 실패 — assistant 응답으로 추가하고 일반 모드로 진행
                    if (!string.IsNullOrEmpty(planText))
                        messages.Add(new ChatMessage { Role = "assistant", Content = planText });
                }
            }

            while (iteration < maxIterations && !ct.IsCancellationRequested)
            {
                iteration++;

                // ── 일시정지 체크포인트: Pause() 호출 시 여기서 대기 ──
                await _pauseSemaphore.WaitAsync(ct).ConfigureAwait(false);
                try
                {
                    // 즉시 릴리즈 — 다음 반복에서도 다시 획득할 수 있도록
                    _pauseSemaphore.Release();
                }
                catch (SemaphoreFullException)
                {
                    // PauseAsync가 아직 세마포어를 보유 중이 아닌 경우 — 무시
                }

                // Phase 33-C: 통합 컨텍스트 관리 — ContextCondenser + AutoCompactMonitor 병합
                // 1단계: 기본 압축 (MaxContextTokens 초과 시)
                // 2단계: 사용량 임계치 초과 시 적극적 압축 (목표 60%)
                if (llm.MaxContextTokens > 0)
                {
                    // Phase 17-B: 압축 전 TaskState Working Memory 주입 — 압축 후에도 작업 상태 유지
                    InjectTaskStateContext(messages);

                    var condensed = await ContextCondenser.CondenseIfNeededAsync(
                        messages, _llm, llm.MaxContextTokens, ct);
                    if (condensed)
                    {
                        EmitEvent(AgentEventType.Thinking, "", "컨텍스트 압축 완료 — 입력 토큰을 절감했습니다");
                        _ = _eventLog?.AppendAsync(AgentEventLogType.CompactionCompleted,
                            JsonSerializer.Serialize(new { messageCount = messages.Count }));
                        UpdateTaskStateSummaryAsync("컨텍스트 압축 완료");
                        // Phase 17-C: PostCompact 훅 (fire-and-forget)
                        _ = RunExtendedEventAsync(HookEventKind.PostCompact, messages, CancellationToken.None);
                    }

                    // 임계치 기반 적극적 압축 (이전 LLM 호출의 토큰 사용량 기준)
                    if (!condensed && _llm.LastTokenUsage != null)
                    {
                        var threshold = llm.AutoCompactThreshold > 0
                            ? llm.AutoCompactThreshold
                            : Defaults.AutoCompactThresholdPercent;
                        var monitor = new AutoCompactMonitor(threshold);
                        if (monitor.ShouldCompact(_llm.LastTokenUsage.PromptTokens, llm.MaxContextTokens))
                        {
                            var usagePct = AutoCompactMonitor.CalculateUsagePercent(
                                _llm.LastTokenUsage.PromptTokens, llm.MaxContextTokens);
                            EmitEvent(AgentEventType.Thinking, "",
                                $"⚠ 컨텍스트 사용량 {usagePct}% — 적극적 컴팩션 실행");
                            _ = _eventLog?.AppendAsync(AgentEventLogType.CompactionTriggered,
                                JsonSerializer.Serialize(new { aggressive = true, usagePct }));
                            // Phase 17-C: PreCompact 훅 — 적극적 압축 직전 (압축 발생 확실)
                            await RunExtendedEventAsync(HookEventKind.PreCompact, messages, ct);
                            var targetTokens = (int)(llm.MaxContextTokens * Defaults.ContextCompressionTargetRatio);
                            var compacted = await ContextCondenser.CondenseIfNeededAsync(
                                messages, _llm, targetTokens, ct);
                            if (compacted)
                            {
                                EmitEvent(AgentEventType.Thinking, "", "적극적 컴팩션 완료");
                                _ = _eventLog?.AppendAsync(AgentEventLogType.CompactionCompleted,
                                    JsonSerializer.Serialize(new { aggressive = true, messageCount = messages.Count }));
                                UpdateTaskStateSummaryAsync($"적극적 컴팩션 완료 (이전 컨텍스트 사용량 {usagePct}%)");
                                // Phase 17-C: PostCompact 훅 (fire-and-forget)
                                _ = RunExtendedEventAsync(HookEventKind.PostCompact, messages, CancellationToken.None);
                            }
                        }
                    }
                }

                EmitEvent(AgentEventType.Thinking, "", $"LLM에 요청 중... (반복 {iteration}/{maxIterations})");

                // 무료 티어 모드: LLM 호출 간 딜레이 (RPM 한도 초과 방지)
                if (llm.FreeTierMode && iteration > 1)
                {
                    var delaySec = llm.FreeTierDelaySeconds > 0 ? llm.FreeTierDelaySeconds : Defaults.FreeTierDelaySeconds;
                    EmitEvent(AgentEventType.Thinking, "", $"무료 티어 모드: {delaySec}초 대기 중...");
                    await Task.Delay(delaySec * 1000, ct);
                }

                // LLM에 도구 정의와 함께 요청
                List<LlmService.ContentBlock> blocks;
                try
                {
                    // Phase 29-B: ToolEnvironmentContext를 사용하여 IConditionalTool 필터링 적용
                    var toolEnv = new ToolEnvironmentContext
                    {
                        Settings = _settings.Settings,
                        ActiveTab = activeTabSnapshot,
                        WorkFolder = llm.WorkFolder,
                        HasGitRepo = !string.IsNullOrEmpty(llm.WorkFolder)
                            && System.IO.Directory.Exists(System.IO.Path.Combine(llm.WorkFolder, ".git")),
                        AiEnabled = _settings.Settings.AiEnabled,
                        InternalModeEnabled = _settings.Settings.InternalModeEnabled,
                    };
                    var activeTools = _tools.GetActiveTools(llm.DisabledTools, toolEnv);
                    blocks = await _llm.SendWithToolsAsync(messages, activeTools, ct);
                }
                catch (NotSupportedException)
                {
                    // Function Calling 미지원 서비스 → 일반 텍스트 응답으로 대체
                    var textResp = await _llm.SendAsync(messages, ct);
                    return textResp;
                }
                catch (ToolCallNotSupportedException ex)
                {
                    // 서버가 도구 호출을 400으로 거부 → 도구 없이 일반 응답으로 폴백
                    LogService.Warn($"[AgentLoop] 도구 호출 거부됨, 일반 응답으로 폴백: {ex.Message}");
                    EmitEvent(AgentEventType.Thinking, "", "도구 호출이 거부되어 일반 응답으로 전환합니다…");

                    // document_plan이 완료됐지만 html_create 미실행 → 조기 종료 전에 앱이 직접 생성
                    if (documentPlanCalled && !string.IsNullOrEmpty(documentPlanScaffold) && !_docFallbackAttempted)
                    {
                        _docFallbackAttempted = true;
                        EmitEvent(AgentEventType.Thinking, "", "앱에서 직접 문서를 생성합니다...");
                        try
                        {
                            var bodyRequest = new List<ChatMessage>
                            {
                                new() { Role = "user",
                                    Content = $"아래 HTML 골격의 각 h2 섹션에 주석의 핵심 항목을 참고하여 " +
                                              $"풍부한 내용을 채워 완전한 HTML body를 출력하세요. " +
                                              $"도구를 호출하지 말고 HTML 코드만 출력하세요.\n\n" +
                                              $"주제: {documentPlanTitle ?? userQuery}\n\n" +
                                              $"골격:\n{documentPlanScaffold}" }
                            };
                            var bodyText = await _llm.SendAsync(bodyRequest, ct);
                            if (!string.IsNullOrEmpty(bodyText))
                            {
                                var htmlTool = _tools.Get("html_create");
                                if (htmlTool != null)
                                {
                                    var fallbackPath = documentPlanPath;
                                    if (string.IsNullOrEmpty(fallbackPath))
                                    {
                                        var safe = userQuery.Length > 40 ? userQuery[..40] : userQuery;
                                        foreach (var c in System.IO.Path.GetInvalidFileNameChars())
                                            safe = safe.Replace(c, '_');
                                        fallbackPath = $"{safe.Trim()}.html";
                                    }
                                    var argsJson = System.Text.Json.JsonSerializer.SerializeToElement(new
                                    {
                                        path     = fallbackPath,
                                        title    = documentPlanTitle ?? userQuery,
                                        body     = bodyText,
                                        toc      = true,
                                        numbered = true,
                                        mood     = "professional",
                                        cover    = new { title = documentPlanTitle ?? userQuery, author = "AX Copilot Agent" }
                                    });
                                    var htmlResult = await htmlTool.ExecuteAsync(argsJson, context, ct);
                                    if (htmlResult.Success)
                                    {
                                        EmitEvent(AgentEventType.ToolResult, "html_create",
                                            $"✅ 보고서 파일 생성: {System.IO.Path.GetFileName(htmlResult.FilePath ?? "")}",
                                            filePath: htmlResult.FilePath);
                                        EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료");
                                        return htmlResult.Output;
                                    }
                                }
                            }
                        }
                        catch (Exception docEx)
                        {
                            LogService.Warn($"[AgentLoop] document_plan 직접 생성 실패: {docEx.Message}");
                        }
                    }

                    try
                    {
                        var textResp = await _llm.SendAsync(messages, ct);
                        return textResp;
                    }
                    catch (Exception fallbackEx)
                    {
                        EmitEvent(AgentEventType.Error, "", $"LLM 오류: {fallbackEx.Message}");
                        return $"⚠ LLM 오류 (도구 호출 실패 후 폴백도 실패): {fallbackEx.Message}";
                    }
                }
                catch (Exception ex)
                {
                    EmitEvent(AgentEventType.Error, "", $"LLM 오류: {ex.Message}");
                    return $"⚠ LLM 오류: {ex.Message}";
                }

                // 응답에서 텍스트와 도구 호출 분리
                var textParts = new List<string>();
                var toolCalls = new List<LlmService.ContentBlock>();

                foreach (var block in blocks)
                {
                    if (block.Type == "text" && !string.IsNullOrWhiteSpace(block.Text))
                        textParts.Add(block.Text);
                    else if (block.Type == "tool_use")
                        toolCalls.Add(block);
                }

                // 텍스트 부분
                var textResponse = string.Join("\n", textParts);

                // Phase 17-B: AssistantMessage 이벤트 기록
                if (!string.IsNullOrWhiteSpace(textResponse))
                    _ = _eventLog?.AppendAsync(AgentEventLogType.AssistantMessage,
                        JsonSerializer.Serialize(new { length = textResponse.Length, hasToolCalls = toolCalls.Count > 0 }));

                // Task Decomposition: 첫 번째 텍스트 응답에서 계획 단계 추출
                if (!planExtracted && !string.IsNullOrEmpty(textResponse))
                {
                    planSteps = TaskDecomposer.ExtractSteps(textResponse);
                    planExtracted = true;

                    if (planSteps.Count > 0)
                    {
                        EmitEvent(AgentEventType.Planning, "", $"작업 계획: {planSteps.Count}단계",
                            steps: planSteps);

                        // 플랜 모드 "auto"에서만 승인 대기
                        // - auto: 계획 감지 시 승인 대기 (단, 도구 호출이 함께 있으면 이미 실행 중이므로 스킵)
                        // - off/always: 승인창 띄우지 않음 (off=자동 진행, always=앞에서 이미 처리됨)
                        var requireApproval = planMode == "auto" && toolCalls.Count == 0;

                        if (requireApproval && UserDecisionCallback != null)
                        {
                            var decision = await UserDecisionCallback(
                                textResponse,
                                new List<string> { "승인", "수정 요청", "취소" });

                            if (decision == "취소")
                            {
                                EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다");
                                return "작업이 취소되었습니다.";
                            }
                            else if (decision != null && decision != "승인")
                            {
                                // 수정 요청 — 사용자 피드백을 메시지에 추가
                                messages.Add(new ChatMessage { Role = "user", Content = decision });
                                EmitEvent(AgentEventType.Thinking, "", "사용자 피드백 반영 중...");
                                planExtracted = false; // 재추출 허용
                                continue; // 루프 재시작 — LLM이 수정된 계획으로 재응답
                            }
                            // 승인(null) — 그대로 진행
        
                        }
                    }
                }

                // Thinking UI: 텍스트 응답 중 도구 호출이 있으면 "사고 과정"으로 표시
                if (!string.IsNullOrEmpty(textResponse) && toolCalls.Count > 0)
                {
                    var thinkingSummary = textResponse.Length > Defaults.ThinkingTextMaxLength
                        ? textResponse[..Defaults.ThinkingTextMaxLength] + "…"
                        : textResponse;
                    EmitEvent(AgentEventType.Thinking, "", thinkingSummary);
                }

                // 도구 호출이 없으면 루프 종료 — 단, 문서 생성 요청인데 파일이 미생성이면 자동 저장
                if (toolCalls.Count == 0)
                {
                    // 계획이 있고 도구가 아직 한 번도 실행되지 않은 경우 → LLM이 도구 대신 텍스트로만 응답한 것
                    // "계획이 승인됐으니 도구를 호출하라"는 메시지를 추가하여 재시도 (최대 2회)
                    if (planSteps.Count > 0 && totalToolCalls == 0 && planExecutionRetry < 2)
                    {
                        planExecutionRetry++;
                        if (!string.IsNullOrEmpty(textResponse))
                            messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
                        messages.Add(new ChatMessage { Role = "user",
                            Content = "도구를 호출하지 않았습니다. 계획 1단계를 지금 즉시 도구(tool call)로 실행하세요. " +
                                      "설명 없이 도구 호출만 하세요." });
                        EmitEvent(AgentEventType.Thinking, "", $"도구 미호출 감지 — 실행 재시도 {planExecutionRetry}/2...");
                        continue; // 루프 재시작
                    }

                    // document_plan은 호출됐지만 terminal 문서 도구(html_create 등)가 미호출인 경우 → 재시도 (최대 2회)
                    if (documentPlanCalled && postDocumentPlanRetry < 2)
                    {
                        postDocumentPlanRetry++;
                        if (!string.IsNullOrEmpty(textResponse))
                            messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
                        messages.Add(new ChatMessage { Role = "user",
                            Content = "html_create 도구를 호출하지 않았습니다. " +
                                      "document_plan 결과의 body 골격을 바탕으로 각 섹션에 충분한 내용을 채워서 " +
                                      "html_create 도구를 지금 즉시 호출하세요. 설명 없이 도구 호출만 하세요." });
                        EmitEvent(AgentEventType.Thinking, "", $"html_create 미호출 재시도 {postDocumentPlanRetry}/2...");
                        continue; // 루프 재시작
                    }

                    // 재시도도 모두 소진 → 앱이 직접 본문 생성 후 html_create 강제 실행
                    if (documentPlanCalled && !string.IsNullOrEmpty(documentPlanScaffold) && !_docFallbackAttempted)
                    {
                        _docFallbackAttempted = true;
                        EmitEvent(AgentEventType.Thinking, "", "LLM이 html_create를 호출하지 않아 앱에서 직접 문서를 생성합니다...");
                        try
                        {
                            // 도구 없이 LLM에게 HTML body 내용만 요청
                            var bodyRequest = new List<ChatMessage>
                            {
                                new() { Role = "user",
                                    Content = $"아래 HTML 골격의 각 h2 섹션에 주석(<!-- -->)의 핵심 항목을 참고하여 " +
                                              $"풍부한 내용을 채워 완전한 HTML body를 출력하세요. " +
                                              $"도구를 호출하지 말고 HTML 코드만 출력하세요.\n\n" +
                                              $"주제: {documentPlanTitle ?? userQuery}\n\n" +
                                              $"골격:\n{documentPlanScaffold}" }
                            };
                            var bodyText = await _llm.SendAsync(bodyRequest, ct);
                            if (!string.IsNullOrEmpty(bodyText))
                            {
                                var htmlTool = _tools.Get("html_create");
                                if (htmlTool != null)
                                {
                                    // 파일명 정규화
                                    var fallbackPath = documentPlanPath;
                                    if (string.IsNullOrEmpty(fallbackPath))
                                    {
                                        var safe = userQuery.Length > 40 ? userQuery[..40] : userQuery;
                                        foreach (var c in System.IO.Path.GetInvalidFileNameChars())
                                            safe = safe.Replace(c, '_');
                                        fallbackPath = $"{safe.Trim()}.html";
                                    }
                                    var argsJson = System.Text.Json.JsonSerializer.SerializeToElement(new
                                    {
                                        path     = fallbackPath,
                                        title    = documentPlanTitle ?? userQuery,
                                        body     = bodyText,
                                        toc      = true,
                                        numbered = true,
                                        mood     = "professional",
                                        cover    = new { title = documentPlanTitle ?? userQuery, author = "AX Copilot Agent" }
                                    });
                                    var htmlResult = await htmlTool.ExecuteAsync(argsJson, context, ct);
                                    if (htmlResult.Success)
                                    {
                                        EmitEvent(AgentEventType.ToolResult, "html_create",
                                            $"✅ 보고서 파일 생성: {System.IO.Path.GetFileName(htmlResult.FilePath ?? "")}",
                                            filePath: htmlResult.FilePath);
                                        textResponse = htmlResult.Output;
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            EmitEvent(AgentEventType.Thinking, "", $"직접 생성 실패: {ex.Message}");
                        }
                    }

                    // LLM이 도구를 한 번도 호출하지 않고 텍스트만 반환 + 문서 생성 요청이면 → 앱이 직접 HTML 파일로 저장
                    // 주의: 이미 도구가 실행된 경우(totalToolCalls > 0)에는 폴백하지 않음 (중복 파일 방지)
                    if (!_docFallbackAttempted && totalToolCalls == 0
                        && !string.IsNullOrEmpty(textResponse)
                        && IsDocumentCreationRequest(userQuery))
                    {
                        _docFallbackAttempted = true;
                        var savedPath = AutoSaveAsHtml(textResponse, userQuery, context);
                        if (savedPath != null)
                        {
                            EmitEvent(AgentEventType.ToolResult, "html_create",
                                $"✅ 보고서 파일 자동 생성: {System.IO.Path.GetFileName(savedPath)}",
                                filePath: savedPath);
                            textResponse += $"\n\n📄 파일이 저장되었습니다: {savedPath}";
                        }
                    }

                    if (!string.IsNullOrEmpty(textResponse))
                        messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
                    EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료");
                    return textResponse;
                }

                // 도구 호출이 있을 때: assistant 메시지에 text + tool_use 블록을 모두 기록
                // (Claude API는 assistant 메시지에 tool_use가 포함되어야 tool_result를 매칭함)
                var contentBlocks = new List<object>();
                if (!string.IsNullOrEmpty(textResponse))
                    contentBlocks.Add(new { type = "text", text = textResponse });
                foreach (var tc in toolCalls)
                    contentBlocks.Add(new { type = "tool_use", id = tc.ToolId, name = tc.ToolName, input = tc.ToolInput });
                var assistantContent = JsonSerializer.Serialize(new { _tool_use_blocks = contentBlocks });
                messages.Add(new ChatMessage { Role = "assistant", Content = assistantContent });

                // 도구 실행 (병렬 모드: 읽기 전용 도구끼리 동시 실행)
                var parallelEnabled = llm.EnableParallelTools && toolCalls.Count > 1;
                if (parallelEnabled)
                {
                    var (parallelBatch, sequentialBatch) = ClassifyToolCalls(toolCalls);
                    if (parallelBatch.Count > 1)
                    {
                        var pState = new ParallelState
                        {
                            CurrentStep = currentStep, TotalToolCalls = totalToolCalls,
                            MaxIterations = maxIterations, ConsecutiveErrors = consecutiveErrors,
                            StatsSuccessCount = statsSuccessCount, StatsFailCount = statsFailCount,
                            StatsInputTokens = statsInputTokens, StatsOutputTokens = statsOutputTokens,
                        };
                        await ExecuteToolsInParallelAsync(parallelBatch, messages, context, planSteps,
                            pState, baseMax, maxRetry, llm, iteration, ct, statsUsedTools);
                        currentStep = pState.CurrentStep; totalToolCalls = pState.TotalToolCalls;
                        maxIterations = pState.MaxIterations; consecutiveErrors = pState.ConsecutiveErrors;
                        statsSuccessCount = pState.StatsSuccessCount; statsFailCount = pState.StatsFailCount;
                        statsInputTokens = pState.StatsInputTokens; statsOutputTokens = pState.StatsOutputTokens;
                    }
                    // 병렬 배치 실행 후 순차 배치 실행
                    toolCalls = sequentialBatch;
                    if (toolCalls.Count == 0) continue;
                }

                // Phase 33-B: 도구 실행 루프 — ProcessSingleToolCallAsync로 위임
                var execState = new ToolExecutionState
                {
                    CurrentStep = currentStep,
                    TotalToolCalls = totalToolCalls,
                    MaxIterations = maxIterations,
                    BaseMax = baseMax,
                    MaxRetry = maxRetry,
                    ConsecutiveErrors = consecutiveErrors,
                    StatsSuccessCount = statsSuccessCount,
                    StatsFailCount = statsFailCount,
                    StatsInputTokens = statsInputTokens,
                    StatsOutputTokens = statsOutputTokens,
                    StatsUsedTools = statsUsedTools,
                    PlanSteps = planSteps,
                    DocumentPlanCalled = documentPlanCalled,
                    DocumentPlanPath = documentPlanPath,
                    DocumentPlanTitle = documentPlanTitle,
                    DocumentPlanScaffold = documentPlanScaffold,
                };
                var loopBreak = false;
                foreach (var call in toolCalls)
                {
                    if (ct.IsCancellationRequested) break;
                    var (action, returnValue) = await ProcessSingleToolCallAsync(
                        call, messages, context, execState, toolCalls, activeTabSnapshot, iteration, ct);
                    if (action == ToolCallAction.Return)
                        return returnValue ?? "";
                    if (action == ToolCallAction.Break) { loopBreak = true; break; }
                }
                // 상태 동기화
                currentStep = execState.CurrentStep;
                totalToolCalls = execState.TotalToolCalls;
                maxIterations = execState.MaxIterations;
                consecutiveErrors = execState.ConsecutiveErrors;
                statsSuccessCount = execState.StatsSuccessCount;
                statsFailCount = execState.StatsFailCount;
                statsInputTokens = execState.StatsInputTokens;
                statsOutputTokens = execState.StatsOutputTokens;
                documentPlanCalled = execState.DocumentPlanCalled;
                documentPlanPath = execState.DocumentPlanPath;
                documentPlanTitle = execState.DocumentPlanTitle;
                documentPlanScaffold = execState.DocumentPlanScaffold;
                if (loopBreak) break;
            }

            if (iteration >= maxIterations)
            {
                EmitEvent(AgentEventType.Error, "", $"최대 반복 횟수 도달 ({maxIterations}회)");
                return "⚠ 에이전트가 최대 반복 횟수에 도달했습니다.";
            }

            return "(취소됨)";
        }
        finally
        {
            // Phase 17-C: SessionEnd + AgentStop 확장 훅 실행 (fire-and-forget)
            _ = RunExtendedEventAsync(HookEventKind.SessionEnd,   null, CancellationToken.None, userMessage: userQuery);
            _ = RunExtendedEventAsync(HookEventKind.AgentStop,    null, CancellationToken.None, userMessage: userQuery);

            // 세션 종료 이벤트 기록
            if (_eventLog != null)
                _ = _eventLog.AppendAsync(AgentEventLogType.SessionEnd,
                    JsonSerializer.Serialize(new { totalToolCalls, iteration }));

            IsRunning = false;

            // 일시정지 상태 리셋
            if (IsPaused)
            {
                IsPaused = false;
                try { _pauseSemaphore.Release(); }
                catch (SemaphoreFullException) { }
            }

            // Phase 33-D: SessionManager — DI 기반 세션 자동 저장
            if (_sessionManager != null)
            {
                try
                {
                    var capturedTab = activeTabSnapshot;
                    var session = new AgentSession
                    {
                        Id = _sessionId ?? Guid.NewGuid().ToString("N")[..12],
                        Name = userQuery.Length > Defaults.QueryNameMaxLength
                            ? userQuery[..Defaults.QueryNameMaxLength] + "…" : userQuery,
                        Tab = capturedTab,
                        WorkFolder = llm.WorkFolder ?? "",
                        Model = llm.Model ?? "",
                        Messages = messages,
                        Tags = new List<string> { capturedTab },
                        CreatedAt = statsStart,
                        UpdatedAt = DateTime.UtcNow,
                    };
                    await _sessionManager.SaveSessionAsync(session);
                }
                catch (Exception ex)
                {
                    EmitEvent(AgentEventType.Error, "", $"세션 저장 실패: {ex.Message}");
                }
            }

            // 통계 기록 (도구 호출이 1회 이상인 세션만)
            if (totalToolCalls > 0)
            {
                var durationMs = (long)(DateTime.Now - statsStart).TotalMilliseconds;

                AgentStatsService.RecordSession(new AgentStatsService.AgentSessionRecord
                {
                    Timestamp    = statsStart,
                    Tab          = activeTabSnapshot,
                    Model        = _settings.Settings.Llm.Model ?? "",
                    ToolCalls    = totalToolCalls,
                    SuccessCount = statsSuccessCount,
                    FailCount    = statsFailCount,
                    InputTokens  = statsInputTokens,
                    OutputTokens = statsOutputTokens,
                    DurationMs   = durationMs,
                    UsedTools    = statsUsedTools,
                });

                // 전체 호출·토큰 합계 표시 (개발자 모드 설정)
                if (llm.ShowTotalCallStats)
                {
                    var totalTokens = statsInputTokens + statsOutputTokens;
                    var durationSec = durationMs / 1000.0;
                    var toolList = string.Join(", ", statsUsedTools);
                    var summary = $"📊 전체 통계: LLM {iteration}회 호출 | 도구 {totalToolCalls}회 (성공 {statsSuccessCount}, 실패 {statsFailCount}) | " +
                                  $"토큰 {statsInputTokens:N0}→{statsOutputTokens:N0} (합계 {totalTokens:N0}) | " +
                                  $"소요 {durationSec:F1}초 | 사용 도구: {toolList}";
                    EmitEvent(AgentEventType.StepDone, "total_stats", summary);
                }

                // Phase 17-A: Reflexion — 세션 완료 후 자기평가 비동기 저장
                var isSessionSuccess = statsSuccessCount > 0 && statsFailCount == 0;
                var lastResult = messages.LastOrDefault(m => m.Role == "assistant")?.Content ?? "";
                FireAndForgetReflexionEval(
                    userQuery, lastResult, _sessionId ?? "",
                    isSessionSuccess, totalToolCalls, iteration);
            }
        }
    }

    private AgentContext BuildContext(string? tabOverride = null)
    {
        var llm = _settings.Settings.Llm;
        return new AgentContext
        {
            Settings = _settings.Settings,
            WorkFolder = llm.WorkFolder,
            Permission = llm.FilePermission,
            BlockedPaths = llm.BlockedPaths,
            BlockedExtensions = llm.BlockedExtensions,
            AskPermission = AskPermissionCallback,
            UserDecision = UserDecisionCallback,
            UserAskCallback = UserAskCallback,
            ToolPermissions = llm.ToolPermissions ?? new(),
            ActiveTab = tabOverride ?? ActiveTab ?? "Chat",
            DevMode = llm.DevMode,
            DevModeStepApproval = llm.DevModeStepApproval,
        };
    }
}
