using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using AxCopilot.Models;

namespace AxCopilot.Services.Agent;

public class AgentLoopService
{
	private class ParallelState
	{
		public int CurrentStep;

		public int TotalToolCalls;

		public int MaxIterations;

		public int ConsecutiveErrors;

		public int StatsSuccessCount;

		public int StatsFailCount;

		public int StatsInputTokens;

		public int StatsOutputTokens;
	}

	private readonly LlmService _llm;

	private readonly ToolRegistry _tools;

	private readonly SettingsService _settings;

	private string _conversationId = "";

	private bool _docFallbackAttempted;

	private readonly SemaphoreSlim _pauseSemaphore = new SemaphoreSlim(1, 1);

	private static readonly HashSet<string> VerificationAllowedTools = new HashSet<string> { "file_read", "directory_list" };

	private static readonly HashSet<string> ReadOnlyTools = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
	{
		"file_read", "glob", "grep_tool", "folder_map", "document_read", "search_codebase", "code_search", "env_tool", "datetime_tool", "dev_env_detect",
		"memory", "skill_manager", "json_tool", "regex_tool", "base64_tool", "hash_tool", "image_analyze"
	};

	public ObservableCollection<AgentEvent> Events { get; } = new ObservableCollection<AgentEvent>();

	public bool IsRunning { get; private set; }

	public Action<Action>? Dispatcher { get; set; }

	public Func<string, string, Task<bool>>? AskPermissionCallback { get; set; }

	public Func<string, List<string>, string, Task<string?>>? UserAskCallback { get; set; }

	public string ActiveTab { get; set; } = "Chat";

	public bool IsPaused { get; private set; }

	public Func<string, List<string>, Task<string?>>? UserDecisionCallback { get; set; }

	public event Action<AgentEvent>? EventOccurred;

	public AgentLoopService(LlmService llm, ToolRegistry tools, SettingsService settings)
	{
		_llm = llm;
		_tools = tools;
		_settings = settings;
	}

	public async Task PauseAsync()
	{
		if (!IsPaused && IsRunning)
		{
			await _pauseSemaphore.WaitAsync().ConfigureAwait(continueOnCapturedContext: false);
			IsPaused = true;
			EmitEvent(AgentEventType.Paused, "", "에이전트가 일시정지되었습니다", null, 0, 0, null, 0L);
		}
	}

	public void Resume()
	{
		if (IsPaused)
		{
			IsPaused = false;
			try
			{
				_pauseSemaphore.Release();
			}
			catch (SemaphoreFullException)
			{
			}
			EmitEvent(AgentEventType.Resumed, "", "에이전트가 재개되었습니다", null, 0, 0, null, 0L);
		}
	}

	public async Task<string> RunAsync(List<ChatMessage> messages, CancellationToken ct = default(CancellationToken))
	{
		if (IsRunning)
		{
			throw new InvalidOperationException("에이전트가 이미 실행 중입니다.");
		}
		IsRunning = true;
		_docFallbackAttempted = false;
		LlmSettings llm = _settings.Settings.Llm;
		int baseMax = ((llm.MaxAgentIterations > 0) ? llm.MaxAgentIterations : 25);
		int maxIterations = baseMax;
		int maxRetry = ((llm.MaxRetryOnError > 0) ? llm.MaxRetryOnError : 3);
		int iteration = 0;
		string userQuery = messages.LastOrDefault((ChatMessage m) => m.Role == "user")?.Content ?? "";
		int consecutiveErrors = 0;
		int totalToolCalls = 0;
		DateTime statsStart = DateTime.Now;
		int statsSuccessCount = 0;
		int statsFailCount = 0;
		int statsInputTokens = 0;
		int statsOutputTokens = 0;
		List<string> statsUsedTools = new List<string>();
		List<string> planSteps = new List<string>();
		int currentStep = 0;
		bool planExtracted = false;
		int planExecutionRetry = 0;
		bool documentPlanCalled = false;
		int postDocumentPlanRetry = 0;
		string documentPlanPath = null;
		string documentPlanTitle = null;
		string documentPlanScaffold = null;
		string planMode = llm.PlanMode ?? "off";
		AgentContext context = BuildContext();
		try
		{
			if (planMode == "always")
			{
				iteration++;
				EmitEvent(AgentEventType.Thinking, "", "실행 계획 생성 중...", null, 0, 0, null, 0L);
				ChatMessage planInstruction = new ChatMessage
				{
					Role = "user",
					Content = "[System] 도구를 호출하지 마세요. 먼저 실행 계획을 번호 매긴 단계로 작성하세요. 각 단계에 사용할 도구와 대상을 구체적으로 명시하세요. 계획만 제시하고 실행은 하지 마세요."
				};
				messages.Add(planInstruction);
				string planText;
				try
				{
					planText = await _llm.SendAsync(messages, ct);
				}
				catch (Exception ex)
				{
					Exception ex2 = ex;
					EmitEvent(AgentEventType.Error, "", "LLM 오류: " + ex2.Message, null, 0, 0, null, 0L);
					return "⚠ LLM 오류: " + ex2.Message;
				}
				messages.Remove(planInstruction);
				planSteps = TaskDecomposer.ExtractSteps(planText);
				planExtracted = true;
				if (planSteps.Count > 0)
				{
					EmitEvent(AgentEventType.Planning, "", $"작업 계획: {planSteps.Count}단계", null, 0, 0, planSteps, 0L);
					if (UserDecisionCallback != null)
					{
						string decision = await UserDecisionCallback(planText, new List<string> { "승인", "수정 요청", "취소" });
						if (decision == "취소")
						{
							EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다", null, 0, 0, null, 0L);
							return "작업이 취소되었습니다.";
						}
						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 < 3; retry++)
							{
								try
								{
									planText = await _llm.SendAsync(messages, ct);
								}
								catch
								{
									break;
								}
								planSteps = TaskDecomposer.ExtractSteps(planText);
								if (planSteps.Count > 0)
								{
									EmitEvent(AgentEventType.Planning, "", $"수정된 계획: {planSteps.Count}단계", null, 0, 0, planSteps, 0L);
								}
								decision = await UserDecisionCallback(planText, new List<string> { "승인", "수정 요청", "취소" });
								if (decision == "취소")
								{
									EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다", null, 0, 0, null, 0L);
									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
					});
					string planSectionsHint = ((planSteps.Count > 0) ? string.Join(", ", planSteps) : "");
					string sectionInstruction = ((!string.IsNullOrEmpty(planSectionsHint)) ? ("document_plan 도구를 호출할 때 sections_hint 파라미터에 위 계획의 섹션/단계를 그대로 넣으세요: \"" + planSectionsHint + "\"") : "");
					messages.Add(new ChatMessage
					{
						Role = "user",
						Content = "계획이 승인되었습니다. 지금 즉시 1단계부터 도구(tool)를 호출하여 실행을 시작하세요. 텍스트로 설명하지 말고 반드시 도구를 호출하세요." + (string.IsNullOrEmpty(sectionInstruction) ? "" : ("\n" + sectionInstruction))
					});
				}
				else if (!string.IsNullOrEmpty(planText))
				{
					messages.Add(new ChatMessage
					{
						Role = "assistant",
						Content = planText
					});
				}
			}
			while (iteration < maxIterations && !ct.IsCancellationRequested)
			{
				iteration++;
				await _pauseSemaphore.WaitAsync(ct).ConfigureAwait(continueOnCapturedContext: false);
				try
				{
					_pauseSemaphore.Release();
				}
				catch (SemaphoreFullException)
				{
				}
				if (await ContextCondenser.CondenseIfNeededAsync(messages, _llm, llm.MaxContextTokens, ct))
				{
					EmitEvent(AgentEventType.Thinking, "", "컨텍스트 압축 완료 — 입력 토큰을 절감했습니다", null, 0, 0, null, 0L);
				}
				EmitEvent(AgentEventType.Thinking, "", $"LLM에 요청 중... (반복 {iteration}/{maxIterations})", null, 0, 0, null, 0L);
				if (llm.FreeTierMode && iteration > 1)
				{
					int delaySec = ((llm.FreeTierDelaySeconds > 0) ? llm.FreeTierDelaySeconds : 4);
					EmitEvent(AgentEventType.Thinking, "", $"무료 티어 모드: {delaySec}초 대기 중...", null, 0, 0, null, 0L);
					await Task.Delay(delaySec * 1000, ct);
				}
				List<LlmService.ContentBlock> blocks;
				try
				{
					IReadOnlyCollection<IAgentTool> activeTools = _tools.GetActiveTools(llm.DisabledTools);
					blocks = await _llm.SendWithToolsAsync(messages, activeTools, ct);
				}
				catch (NotSupportedException)
				{
					return await _llm.SendAsync(messages, ct);
				}
				catch (ToolCallNotSupportedException ex5)
				{
					LogService.Warn("[AgentLoop] 도구 호출 거부됨, 일반 응답으로 폴백: " + ex5.Message);
					EmitEvent(AgentEventType.Thinking, "", "도구 호출이 거부되어 일반 응답으로 전환합니다…", null, 0, 0, null, 0L);
					if (documentPlanCalled && !string.IsNullOrEmpty(documentPlanScaffold) && !_docFallbackAttempted)
					{
						_docFallbackAttempted = true;
						EmitEvent(AgentEventType.Thinking, "", "앱에서 직접 문서를 생성합니다...", null, 0, 0, null, 0L);
						try
						{
							List<ChatMessage> bodyRequest = new List<ChatMessage>
							{
								new ChatMessage
								{
									Role = "user",
									Content = $"아래 HTML 골격의 각 h2 섹션에 주석의 핵심 항목을 참고하여 풍부한 내용을 채워 완전한 HTML body를 출력하세요. 도구를 호출하지 말고 HTML 코드만 출력하세요.\n\n주제: {documentPlanTitle ?? userQuery}\n\n골격:\n{documentPlanScaffold}"
								}
							};
							string bodyText = await _llm.SendAsync(bodyRequest, ct);
							if (!string.IsNullOrEmpty(bodyText))
							{
								IAgentTool htmlTool = _tools.Get("html_create");
								if (htmlTool != null)
								{
									string fallbackPath = documentPlanPath;
									if (string.IsNullOrEmpty(fallbackPath))
									{
										string safe = ((userQuery.Length > 40) ? userQuery.Substring(0, 40) : userQuery);
										char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
										foreach (char c in invalidFileNameChars)
										{
											safe = safe.Replace(c, '_');
										}
										fallbackPath = safe.Trim() + ".html";
									}
									JsonElement argsJson = 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"
										}
									});
									ToolResult htmlResult = await htmlTool.ExecuteAsync(argsJson, context, ct);
									if (htmlResult.Success)
									{
										EmitEvent(AgentEventType.ToolResult, "html_create", "✅ 보고서 파일 생성: " + Path.GetFileName(htmlResult.FilePath ?? ""), htmlResult.FilePath, 0, 0, null, 0L);
										EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료", null, 0, 0, null, 0L);
										return htmlResult.Output;
									}
								}
							}
						}
						catch (Exception ex)
						{
							Exception docEx = ex;
							LogService.Warn("[AgentLoop] document_plan 직접 생성 실패: " + docEx.Message);
						}
					}
					try
					{
						return await _llm.SendAsync(messages, ct);
					}
					catch (Exception ex6)
					{
						EmitEvent(AgentEventType.Error, "", "LLM 오류: " + ex6.Message, null, 0, 0, null, 0L);
						return "⚠ LLM 오류 (도구 호출 실패 후 폴백도 실패): " + ex6.Message;
					}
				}
				catch (Exception ex7)
				{
					EmitEvent(AgentEventType.Error, "", "LLM 오류: " + ex7.Message, null, 0, 0, null, 0L);
					return "⚠ LLM 오류: " + ex7.Message;
				}
				List<string> textParts = new List<string>();
				List<LlmService.ContentBlock> toolCalls = new List<LlmService.ContentBlock>();
				foreach (LlmService.ContentBlock block in blocks)
				{
					if (block.Type == "text" && !string.IsNullOrWhiteSpace(block.Text))
					{
						textParts.Add(block.Text);
					}
					else if (block.Type == "tool_use")
					{
						toolCalls.Add(block);
					}
				}
				string textResponse = string.Join("\n", textParts);
				if (!planExtracted && !string.IsNullOrEmpty(textResponse))
				{
					planSteps = TaskDecomposer.ExtractSteps(textResponse);
					planExtracted = true;
					if (planSteps.Count > 0)
					{
						EmitEvent(AgentEventType.Planning, "", $"작업 계획: {planSteps.Count}단계", null, 0, 0, planSteps, 0L);
						if (planMode == "auto" && toolCalls.Count == 0 && UserDecisionCallback != null)
						{
							string decision2 = await UserDecisionCallback(textResponse, new List<string> { "승인", "수정 요청", "취소" });
							if (decision2 == "취소")
							{
								EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다", null, 0, 0, null, 0L);
								return "작업이 취소되었습니다.";
							}
							if (decision2 != null && decision2 != "승인")
							{
								messages.Add(new ChatMessage
								{
									Role = "user",
									Content = decision2
								});
								EmitEvent(AgentEventType.Thinking, "", "사용자 피드백 반영 중...", null, 0, 0, null, 0L);
								planExtracted = false;
								continue;
							}
						}
					}
				}
				if (!string.IsNullOrEmpty(textResponse) && toolCalls.Count > 0)
				{
					string thinkingSummary = ((textResponse.Length > 150) ? (textResponse.Substring(0, 150) + "…") : textResponse);
					EmitEvent(AgentEventType.Thinking, "", thinkingSummary, null, 0, 0, null, 0L);
				}
				if (toolCalls.Count == 0)
				{
					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...", null, 0, 0, null, 0L);
						continue;
					}
					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...", null, 0, 0, null, 0L);
						continue;
					}
					if (documentPlanCalled && !string.IsNullOrEmpty(documentPlanScaffold) && !_docFallbackAttempted)
					{
						_docFallbackAttempted = true;
						EmitEvent(AgentEventType.Thinking, "", "LLM이 html_create를 호출하지 않아 앱에서 직접 문서를 생성합니다...", null, 0, 0, null, 0L);
						try
						{
							List<ChatMessage> bodyRequest2 = new List<ChatMessage>
							{
								new ChatMessage
								{
									Role = "user",
									Content = $"아래 HTML 골격의 각 h2 섹션에 주석(<!-- -->)의 핵심 항목을 참고하여 풍부한 내용을 채워 완전한 HTML body를 출력하세요. 도구를 호출하지 말고 HTML 코드만 출력하세요.\n\n주제: {documentPlanTitle ?? userQuery}\n\n골격:\n{documentPlanScaffold}"
								}
							};
							string bodyText2 = await _llm.SendAsync(bodyRequest2, ct);
							if (!string.IsNullOrEmpty(bodyText2))
							{
								IAgentTool htmlTool2 = _tools.Get("html_create");
								if (htmlTool2 != null)
								{
									string fallbackPath2 = documentPlanPath;
									if (string.IsNullOrEmpty(fallbackPath2))
									{
										string safe2 = ((userQuery.Length > 40) ? userQuery.Substring(0, 40) : userQuery);
										char[] invalidFileNameChars2 = Path.GetInvalidFileNameChars();
										foreach (char c2 in invalidFileNameChars2)
										{
											safe2 = safe2.Replace(c2, '_');
										}
										fallbackPath2 = safe2.Trim() + ".html";
									}
									JsonElement argsJson2 = JsonSerializer.SerializeToElement(new
									{
										path = fallbackPath2,
										title = (documentPlanTitle ?? userQuery),
										body = bodyText2,
										toc = true,
										numbered = true,
										mood = "professional",
										cover = new
										{
											title = (documentPlanTitle ?? userQuery),
											author = "AX Copilot Agent"
										}
									});
									ToolResult htmlResult2 = await htmlTool2.ExecuteAsync(argsJson2, context, ct);
									if (htmlResult2.Success)
									{
										EmitEvent(AgentEventType.ToolResult, "html_create", "✅ 보고서 파일 생성: " + Path.GetFileName(htmlResult2.FilePath ?? ""), htmlResult2.FilePath, 0, 0, null, 0L);
										textResponse = htmlResult2.Output;
									}
								}
							}
						}
						catch (Exception ex)
						{
							Exception ex8 = ex;
							EmitEvent(AgentEventType.Thinking, "", "직접 생성 실패: " + ex8.Message, null, 0, 0, null, 0L);
						}
					}
					if (!_docFallbackAttempted && totalToolCalls == 0 && !string.IsNullOrEmpty(textResponse) && IsDocumentCreationRequest(userQuery))
					{
						_docFallbackAttempted = true;
						string savedPath = AutoSaveAsHtml(textResponse, userQuery, context);
						if (savedPath != null)
						{
							EmitEvent(AgentEventType.ToolResult, "html_create", "✅ 보고서 파일 자동 생성: " + Path.GetFileName(savedPath), savedPath, 0, 0, null, 0L);
							textResponse = textResponse + "\n\n\ud83d\udcc4 파일이 저장되었습니다: " + savedPath;
						}
					}
					if (!string.IsNullOrEmpty(textResponse))
					{
						messages.Add(new ChatMessage
						{
							Role = "assistant",
							Content = textResponse
						});
					}
					EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료", null, 0, 0, null, 0L);
					return textResponse;
				}
				List<object> contentBlocks = new List<object>();
				if (!string.IsNullOrEmpty(textResponse))
				{
					contentBlocks.Add(new
					{
						type = "text",
						text = textResponse
					});
				}
				foreach (LlmService.ContentBlock tc in toolCalls)
				{
					contentBlocks.Add(new
					{
						type = "tool_use",
						id = tc.ToolId,
						name = tc.ToolName,
						input = tc.ToolInput
					});
				}
				string assistantContent = JsonSerializer.Serialize(new
				{
					_tool_use_blocks = contentBlocks
				});
				messages.Add(new ChatMessage
				{
					Role = "assistant",
					Content = assistantContent
				});
				if (llm.EnableParallelTools && toolCalls.Count > 1)
				{
					List<LlmService.ContentBlock> parallelBatch;
					List<LlmService.ContentBlock> sequentialBatch;
					(parallelBatch, sequentialBatch) = ClassifyToolCalls(toolCalls);
					if (parallelBatch.Count > 1)
					{
						ParallelState 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;
					}
				}
				foreach (LlmService.ContentBlock call in toolCalls)
				{
					if (ct.IsCancellationRequested)
					{
						break;
					}
					IAgentTool tool = _tools.Get(call.ToolName);
					if (tool == null)
					{
						string errResult = "알 수 없는 도구: " + call.ToolName;
						EmitEvent(AgentEventType.Error, call.ToolName, errResult, null, 0, 0, null, 0L);
						messages.Add(LlmService.CreateToolResultMessage(call.ToolId, call.ToolName, errResult));
						continue;
					}
					if (planSteps.Count > 0)
					{
						int newStep = TaskDecomposer.EstimateCurrentStep(toolSummary: FormatToolCallSummary(call), steps: planSteps, toolName: call.ToolName, lastStep: currentStep);
						if (newStep != currentStep)
						{
							currentStep = newStep;
							EmitEvent(AgentEventType.StepStart, "", planSteps[currentStep], null, currentStep + 1, planSteps.Count, null, 0L);
						}
					}
					if (context.DevMode)
					{
						string paramJson = call.ToolInput?.ToString() ?? "{}";
						if (paramJson.Length > 500)
						{
							paramJson = paramJson.Substring(0, 500) + "...";
						}
						EmitEvent(AgentEventType.Thinking, call.ToolName, "[DEV] 도구 호출: " + call.ToolName + "\n파라미터: " + paramJson, null, 0, 0, null, 0L);
					}
					EmitEvent(AgentEventType.ToolCall, call.ToolName, FormatToolCallSummary(call), null, 0, 0, null, 0L);
					if (context.DevModeStepApproval && UserDecisionCallback != null)
					{
						string decision3 = await UserDecisionCallback("[DEV] 도구 '" + call.ToolName + "' 실행을 승인하시겠습니까?\n" + FormatToolCallSummary(call), new List<string> { "승인", "건너뛰기", "중단" });
						if (decision3 == "중단")
						{
							EmitEvent(AgentEventType.Complete, "", "[DEV] 사용자가 실행을 중단했습니다", null, 0, 0, null, 0L);
							return "사용자가 개발자 모드에서 실행을 중단했습니다.";
						}
						if (decision3 == "건너뛰기")
						{
							messages.Add(LlmService.CreateToolResultMessage(call.ToolId, call.ToolName, "[SKIPPED by developer] 사용자가 이 도구 실행을 건너뛰었습니다."));
							continue;
						}
					}
					string decisionRequired = CheckDecisionRequired(call, context);
					if (decisionRequired != null && UserDecisionCallback != null)
					{
						string decision4 = await UserDecisionCallback(decisionRequired, new List<string> { "승인", "건너뛰기", "취소" });
						if (decision4 == "취소")
						{
							EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다", null, 0, 0, null, 0L);
							return "사용자가 작업을 취소했습니다.";
						}
						if (decision4 == "건너뛰기")
						{
							messages.Add(LlmService.CreateToolResultMessage(call.ToolId, call.ToolName, "[SKIPPED] 사용자가 이 작업을 건너뛰었습니다."));
							continue;
						}
					}
					if (llm.EnableToolHooks && llm.AgentHooks.Count > 0)
					{
						try
						{
							foreach (HookExecutionResult pr in (await AgentHookRunner.RunAsync(llm.AgentHooks, call.ToolName, "pre", call.ToolInput.ToString(), null, success: true, context.WorkFolder, llm.ToolHookTimeoutMs, ct)).Where((HookExecutionResult r) => !r.Success))
							{
								EmitEvent(AgentEventType.Error, call.ToolName, "[Hook:" + pr.HookName + "] " + pr.Output, null, 0, 0, null, 0L);
							}
						}
						catch
						{
						}
					}
					Stopwatch sw = Stopwatch.StartNew();
					ToolResult result;
					try
					{
						JsonElement input = call.ToolInput ?? JsonDocument.Parse("{}").RootElement;
						result = await tool.ExecuteAsync(input, context, ct);
					}
					catch (OperationCanceledException)
					{
						EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다.", null, 0, 0, null, 0L);
						return "사용자가 작업을 취소했습니다.";
					}
					catch (Exception ex10)
					{
						result = ToolResult.Fail("도구 실행 오류: " + ex10.Message);
					}
					sw.Stop();
					if (llm.EnableToolHooks && llm.AgentHooks.Count > 0)
					{
						try
						{
							foreach (HookExecutionResult pr2 in (await AgentHookRunner.RunAsync(llm.AgentHooks, call.ToolName, "post", call.ToolInput.ToString(), TruncateOutput(result.Output, 2048), result.Success, context.WorkFolder, llm.ToolHookTimeoutMs, ct)).Where((HookExecutionResult r) => !r.Success))
							{
								EmitEvent(AgentEventType.Error, call.ToolName, "[Hook:" + pr2.HookName + "] " + pr2.Output, null, 0, 0, null, 0L);
							}
						}
						catch
						{
						}
					}
					if (context.DevMode)
					{
						EmitEvent(AgentEventType.Thinking, call.ToolName, "[DEV] 결과: " + (result.Success ? "성공" : "실패") + "\n" + TruncateOutput(result.Output, 500), null, 0, 0, null, 0L);
					}
					TokenUsage tokenUsage = _llm.LastTokenUsage;
					EmitEvent(result.Success ? AgentEventType.ToolResult : AgentEventType.Error, call.ToolName, TruncateOutput(result.Output, 200), result.FilePath, 0, 0, null, sw.ElapsedMilliseconds, tokenUsage?.PromptTokens ?? 0, tokenUsage?.CompletionTokens ?? 0, call.ToolInput?.ToString(), iteration);
					if (result.Success)
					{
						statsSuccessCount++;
					}
					else
					{
						statsFailCount++;
					}
					statsInputTokens += tokenUsage?.PromptTokens ?? 0;
					statsOutputTokens += tokenUsage?.CompletionTokens ?? 0;
					if (!statsUsedTools.Contains(call.ToolName))
					{
						statsUsedTools.Add(call.ToolName);
					}
					if (llm.EnableAuditLog)
					{
						AuditLogService.LogToolCall(_conversationId, ActiveTab ?? "", call.ToolName, call.ToolInput.ToString() ?? "", TruncateOutput(result.Output, 500), result.FilePath, result.Success);
					}
					totalToolCalls++;
					if (totalToolCalls > 15 && maxIterations < baseMax * 2)
					{
						maxIterations = Math.Min(baseMax * 2, 50);
					}
					if (call.ToolName == "test_loop" && result.Output.Contains("[AUTO_FIX:"))
					{
						int testFixMax = ((llm.MaxTestFixIterations > 0) ? llm.MaxTestFixIterations : 5);
						int testFixBudget = baseMax + testFixMax * 3;
						if (maxIterations < testFixBudget)
						{
							maxIterations = Math.Min(testFixBudget, 60);
						}
					}
					await Task.Delay(80, ct);
					if (!result.Success)
					{
						consecutiveErrors++;
						if (consecutiveErrors <= maxRetry)
						{
							messages.Add(LlmService.CreateToolResultMessage(result: $"[Tool '{call.ToolName}' failed: {TruncateOutput(result.Output, 500)}]\nAnalyze why this failed. Consider: wrong parameters, wrong file path, missing prerequisites. Try a different approach. (Error {consecutiveErrors}/{maxRetry})", toolId: call.ToolId, toolName: call.ToolName));
							EmitEvent(AgentEventType.Thinking, "", $"Self-Reflection: 실패 분석 후 재시도 ({consecutiveErrors}/{maxRetry})", null, 0, 0, null, 0L);
							continue;
						}
						messages.Add(LlmService.CreateToolResultMessage(call.ToolId, call.ToolName, $"[FAILED after {maxRetry} retries] {TruncateOutput(result.Output, 500)}\n" + "Stop retrying this tool. Explain the error to the user and suggest alternative approaches."));
						try
						{
							App app = Application.Current as App;
							AgentMemoryService memSvc = app?.MemoryService;
							if (memSvc != null && app?.SettingsService?.Settings.Llm.EnableAgentMemory == true)
							{
								memSvc.Add("correction", "도구 '" + call.ToolName + "' 반복 실패: " + TruncateOutput(result.Output, 200), "conv:" + _conversationId, context.WorkFolder);
							}
						}
						catch
						{
						}
						continue;
					}
					consecutiveErrors = 0;
					messages.Add(LlmService.CreateToolResultMessage(call.ToolId, call.ToolName, TruncateOutput(result.Output, 4000)));
					if (call.ToolName == "document_plan")
					{
						documentPlanCalled = true;
						string po = result.Output;
						Match pm = Regex.Match(po, "path:\\s*\"([^\"]+)\"");
						if (pm.Success)
						{
							documentPlanPath = pm.Groups[1].Value;
						}
						Match tm = Regex.Match(po, "title:\\s*\"([^\"]+)\"");
						if (tm.Success)
						{
							documentPlanTitle = tm.Groups[1].Value;
						}
						int bs = po.IndexOf("--- body 시작 ---", StringComparison.Ordinal);
						int be = po.IndexOf("--- body 끝 ---", StringComparison.Ordinal);
						if (bs >= 0 && be > bs)
						{
							int num3 = bs + "--- body 시작 ---".Length;
							documentPlanScaffold = po.Substring(num3, be - num3).Trim();
						}
					}
					if (call.ToolName == "document_plan" && result.Output.Contains("즉시 실행:"))
					{
						string toolHint = (result.Output.Contains("html_create") ? "html_create" : (result.Output.Contains("document_assemble") ? "document_assemble" : (result.Output.Contains("file_write") ? "file_write" : "html_create")));
						messages.Add(new ChatMessage
						{
							Role = "user",
							Content = $"document_plan이 완료되었습니다. 위 결과의 body/sections의 [내용...] 부분을 실제 상세 내용으로 모두 채워서 {toolHint} 도구를 지금 즉시 호출하세요. 각 섹션마다 반드시 충분한 내용을 작성하고, 설명 없이 도구를 바로 호출하세요."
						});
						EmitEvent(AgentEventType.Thinking, "", "문서 개요 완성 — " + toolHint + " 호출 중...", null, 0, 0, null, 0L);
					}
					if (result.Success && IsTerminalDocumentTool(call.ToolName) && toolCalls.Count == 1)
					{
						if ((!(ActiveTab == "Code")) ? (llm.EnableCoworkVerification && IsDocumentCreationTool(call.ToolName)) : (llm.Code.EnableCodeVerification && IsCodeVerificationTarget(call.ToolName)))
						{
							await RunPostToolVerificationAsync(messages, call.ToolName, result, context, ct);
							iteration++;
						}
						EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료", null, 0, 0, null, 0L);
						return result.Output;
					}
					if (((!(ActiveTab == "Code")) ? (llm.EnableCoworkVerification && IsDocumentCreationTool(call.ToolName)) : (llm.Code.EnableCodeVerification && IsCodeVerificationTarget(call.ToolName))) && result.Success)
					{
						await RunPostToolVerificationAsync(messages, call.ToolName, result, context, ct);
						iteration++;
					}
				}
			}
			if (iteration >= maxIterations)
			{
				EmitEvent(AgentEventType.Error, "", $"최대 반복 횟수 도달 ({maxIterations}회)", null, 0, 0, null, 0L);
				return "⚠ 에이전트가 최대 반복 횟수에 도달했습니다.";
			}
			return "(취소됨)";
		}
		finally
		{
			IsRunning = false;
			if (IsPaused)
			{
				IsPaused = false;
				try
				{
					_pauseSemaphore.Release();
				}
				catch (SemaphoreFullException)
				{
				}
			}
			if (totalToolCalls > 0)
			{
				long durationMs = (long)(DateTime.Now - statsStart).TotalMilliseconds;
				AgentStatsService.RecordSession(new AgentStatsService.AgentSessionRecord
				{
					Timestamp = statsStart,
					Tab = (ActiveTab ?? ""),
					Model = (_settings.Settings.Llm.Model ?? ""),
					ToolCalls = totalToolCalls,
					SuccessCount = statsSuccessCount,
					FailCount = statsFailCount,
					InputTokens = statsInputTokens,
					OutputTokens = statsOutputTokens,
					DurationMs = durationMs,
					UsedTools = statsUsedTools
				});
				if (llm.ShowTotalCallStats)
				{
					int totalTokens = statsInputTokens + statsOutputTokens;
					double durationSec = (double)durationMs / 1000.0;
					string toolList = string.Join(", ", statsUsedTools);
					string summary = $"\ud83d\udcca 전체 통계: LLM {iteration}회 호출 | 도구 {totalToolCalls}회 (성공 {statsSuccessCount}, 실패 {statsFailCount}) | 토큰 {statsInputTokens:N0}→{statsOutputTokens:N0} (합계 {totalTokens:N0}) | 소요 {durationSec:F1}초 | 사용 도구: {toolList}";
					EmitEvent(AgentEventType.StepDone, "total_stats", summary, null, 0, 0, null, 0L);
				}
			}
		}
	}

	private string? AutoSaveAsHtml(string textContent, string userQuery, AgentContext context)
	{
		try
		{
			string text = ((userQuery.Length > 60) ? userQuery.Substring(0, 60) : userQuery);
			string[] array = new string[14]
			{
				"작성해줘", "작성해 줘", "만들어줘", "만들어 줘", "써줘", "써 줘", "생성해줘", "생성해 줘", "작성해", "만들어",
				"생성해", "해줘", "해 줘", "부탁해"
			};
			string text2 = text;
			string[] array2 = array;
			foreach (string oldValue in array2)
			{
				text2 = text2.Replace(oldValue, "", StringComparison.OrdinalIgnoreCase);
			}
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			foreach (char oldChar in invalidFileNameChars)
			{
				text2 = text2.Replace(oldChar, '_');
			}
			text2 = text2.Trim().TrimEnd('.').Trim();
			string path = text2 + ".html";
			string text3 = FileReadTool.ResolvePath(path, context.WorkFolder);
			if (context.ActiveTab == "Cowork")
			{
				text3 = AgentContext.EnsureTimestampedPath(text3);
			}
			string directoryName = Path.GetDirectoryName(text3);
			if (!string.IsNullOrEmpty(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
			string css = TemplateService.GetCss("professional");
			string value = ConvertTextToHtml(textContent);
			string contents = $"<!DOCTYPE html>\n<html lang=\"ko\">\n<head>\n<meta charset=\"utf-8\">\n<title>{EscapeHtml(text)}</title>\n<style>\n{css}\n.doc {{ max-width: 900px; margin: 0 auto; padding: 40px 30px 60px; }}\n.doc h1 {{ font-size: 28px; margin-bottom: 8px; border-bottom: 3px solid var(--accent, #4B5EFC); padding-bottom: 10px; }}\n.doc h2 {{ font-size: 22px; margin-top: 36px; margin-bottom: 12px; border-bottom: 2px solid var(--accent, #4B5EFC); padding-bottom: 6px; }}\n.doc h3 {{ font-size: 18px; margin-top: 24px; margin-bottom: 8px; }}\n.doc .meta {{ color: #888; font-size: 13px; margin-bottom: 24px; }}\n.doc p {{ line-height: 1.8; margin-bottom: 12px; }}\n.doc ul, .doc ol {{ line-height: 1.8; margin-bottom: 16px; }}\n.doc table {{ border-collapse: collapse; width: 100%; margin: 16px 0; }}\n.doc th {{ background: var(--accent, #4B5EFC); color: #fff; padding: 10px 14px; text-align: left; }}\n.doc td {{ padding: 8px 14px; border-bottom: 1px solid #e5e7eb; }}\n.doc tr:nth-child(even) {{ background: #f8f9fa; }}\n</style>\n</head>\n<body>\n<div class=\"doc\">\n<h1>{EscapeHtml(text)}</h1>\n<div class=\"meta\">작성일: {DateTime.Now:yyyy-MM-dd} | AX Copilot 자동 생성</div>\n{value}\n</div>\n</body>\n</html>";
			File.WriteAllText(text3, contents, Encoding.UTF8);
			LogService.Info("[AgentLoop] 문서 자동 저장 완료: " + text3);
			return text3;
		}
		catch (Exception ex)
		{
			LogService.Warn("[AgentLoop] 문서 자동 저장 실패: " + ex.Message);
			return null;
		}
	}

	private static string ConvertTextToHtml(string text)
	{
		StringBuilder stringBuilder = new StringBuilder();
		string[] array = text.Split('\n');
		bool flag = false;
		string value = "ul";
		string[] array2 = array;
		foreach (string text2 in array2)
		{
			string text3 = text2.TrimEnd();
			if (string.IsNullOrWhiteSpace(text3))
			{
				if (flag)
				{
					StringBuilder stringBuilder2 = stringBuilder;
					StringBuilder stringBuilder3 = stringBuilder2;
					StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(3, 1, stringBuilder2);
					handler.AppendLiteral("</");
					handler.AppendFormatted(value);
					handler.AppendLiteral(">");
					stringBuilder3.AppendLine(ref handler);
					flag = false;
				}
			}
			else if (text3.StartsWith("### "))
			{
				StringBuilder stringBuilder2;
				StringBuilder.AppendInterpolatedStringHandler handler;
				if (flag)
				{
					stringBuilder2 = stringBuilder;
					StringBuilder stringBuilder4 = stringBuilder2;
					handler = new StringBuilder.AppendInterpolatedStringHandler(3, 1, stringBuilder2);
					handler.AppendLiteral("</");
					handler.AppendFormatted(value);
					handler.AppendLiteral(">");
					stringBuilder4.AppendLine(ref handler);
					flag = false;
				}
				stringBuilder2 = stringBuilder;
				StringBuilder stringBuilder5 = stringBuilder2;
				handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
				handler.AppendLiteral("<h3>");
				string text4 = text3;
				handler.AppendFormatted(EscapeHtml(text4.Substring(4, text4.Length - 4)));
				handler.AppendLiteral("</h3>");
				stringBuilder5.AppendLine(ref handler);
			}
			else if (text3.StartsWith("## "))
			{
				StringBuilder stringBuilder2;
				StringBuilder.AppendInterpolatedStringHandler handler;
				if (flag)
				{
					stringBuilder2 = stringBuilder;
					StringBuilder stringBuilder6 = stringBuilder2;
					handler = new StringBuilder.AppendInterpolatedStringHandler(3, 1, stringBuilder2);
					handler.AppendLiteral("</");
					handler.AppendFormatted(value);
					handler.AppendLiteral(">");
					stringBuilder6.AppendLine(ref handler);
					flag = false;
				}
				stringBuilder2 = stringBuilder;
				StringBuilder stringBuilder7 = stringBuilder2;
				handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
				handler.AppendLiteral("<h2>");
				string text4 = text3;
				handler.AppendFormatted(EscapeHtml(text4.Substring(3, text4.Length - 3)));
				handler.AppendLiteral("</h2>");
				stringBuilder7.AppendLine(ref handler);
			}
			else if (text3.StartsWith("# "))
			{
				StringBuilder stringBuilder2;
				StringBuilder.AppendInterpolatedStringHandler handler;
				if (flag)
				{
					stringBuilder2 = stringBuilder;
					StringBuilder stringBuilder8 = stringBuilder2;
					handler = new StringBuilder.AppendInterpolatedStringHandler(3, 1, stringBuilder2);
					handler.AppendLiteral("</");
					handler.AppendFormatted(value);
					handler.AppendLiteral(">");
					stringBuilder8.AppendLine(ref handler);
					flag = false;
				}
				stringBuilder2 = stringBuilder;
				StringBuilder stringBuilder9 = stringBuilder2;
				handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
				handler.AppendLiteral("<h2>");
				string text4 = text3;
				handler.AppendFormatted(EscapeHtml(text4.Substring(2, text4.Length - 2)));
				handler.AppendLiteral("</h2>");
				stringBuilder9.AppendLine(ref handler);
			}
			else if (Regex.IsMatch(text3, "^\\d+\\.\\s+\\S"))
			{
				string text5 = Regex.Replace(text3, "^\\d+\\.\\s+", "");
				if (text5.Length < 80 && !text5.Contains('.') && !text3.StartsWith("  "))
				{
					StringBuilder stringBuilder2;
					StringBuilder.AppendInterpolatedStringHandler handler;
					if (flag)
					{
						stringBuilder2 = stringBuilder;
						StringBuilder stringBuilder10 = stringBuilder2;
						handler = new StringBuilder.AppendInterpolatedStringHandler(3, 1, stringBuilder2);
						handler.AppendLiteral("</");
						handler.AppendFormatted(value);
						handler.AppendLiteral(">");
						stringBuilder10.AppendLine(ref handler);
						flag = false;
					}
					stringBuilder2 = stringBuilder;
					StringBuilder stringBuilder11 = stringBuilder2;
					handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
					handler.AppendLiteral("<h2>");
					handler.AppendFormatted(EscapeHtml(text3));
					handler.AppendLiteral("</h2>");
					stringBuilder11.AppendLine(ref handler);
				}
				else
				{
					if (!flag)
					{
						stringBuilder.AppendLine("<ol>");
						flag = true;
						value = "ol";
					}
					StringBuilder stringBuilder2 = stringBuilder;
					StringBuilder stringBuilder12 = stringBuilder2;
					StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
					handler.AppendLiteral("<li>");
					handler.AppendFormatted(EscapeHtml(text5));
					handler.AppendLiteral("</li>");
					stringBuilder12.AppendLine(ref handler);
				}
			}
			else if (text3.TrimStart().StartsWith("- ") || text3.TrimStart().StartsWith("* ") || text3.TrimStart().StartsWith("• "))
			{
				string text4 = text3.TrimStart();
				string text6 = text4.Substring(2, text4.Length - 2).Trim();
				if (!flag)
				{
					stringBuilder.AppendLine("<ul>");
					flag = true;
					value = "ul";
				}
				StringBuilder stringBuilder2 = stringBuilder;
				StringBuilder stringBuilder13 = stringBuilder2;
				StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
				handler.AppendLiteral("<li>");
				handler.AppendFormatted(EscapeHtml(text6));
				handler.AppendLiteral("</li>");
				stringBuilder13.AppendLine(ref handler);
			}
			else
			{
				StringBuilder stringBuilder2;
				StringBuilder.AppendInterpolatedStringHandler handler;
				if (flag)
				{
					stringBuilder2 = stringBuilder;
					StringBuilder stringBuilder14 = stringBuilder2;
					handler = new StringBuilder.AppendInterpolatedStringHandler(3, 1, stringBuilder2);
					handler.AppendLiteral("</");
					handler.AppendFormatted(value);
					handler.AppendLiteral(">");
					stringBuilder14.AppendLine(ref handler);
					flag = false;
				}
				stringBuilder2 = stringBuilder;
				StringBuilder stringBuilder15 = stringBuilder2;
				handler = new StringBuilder.AppendInterpolatedStringHandler(7, 1, stringBuilder2);
				handler.AppendLiteral("<p>");
				handler.AppendFormatted(EscapeHtml(text3));
				handler.AppendLiteral("</p>");
				stringBuilder15.AppendLine(ref handler);
			}
		}
		if (flag)
		{
			StringBuilder stringBuilder2 = stringBuilder;
			StringBuilder stringBuilder16 = stringBuilder2;
			StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(3, 1, stringBuilder2);
			handler.AppendLiteral("</");
			handler.AppendFormatted(value);
			handler.AppendLiteral(">");
			stringBuilder16.AppendLine(ref handler);
		}
		return stringBuilder.ToString();
	}

	private static string EscapeHtml(string text)
	{
		return text.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;");
	}

	private static bool IsDocumentCreationRequest(string query)
	{
		if (string.IsNullOrWhiteSpace(query))
		{
			return false;
		}
		string[] source = new string[24]
		{
			"보고서", "리포트", "report", "문서", "작성해", "써줘", "써 줘", "만들어", "분석서", "제안서",
			"기획서", "회의록", "매뉴얼", "가이드", "excel", "엑셀", "docx", "word", "html", "pptx",
			"ppt", "프레젠테이션", "발표자료", "슬라이드"
		};
		string q = query.ToLowerInvariant();
		return source.Any((string k) => q.Contains(k, StringComparison.OrdinalIgnoreCase));
	}

	private static bool IsDocumentCreationTool(string toolName)
	{
		switch (toolName)
		{
		case "file_write":
		case "docx_create":
		case "html_create":
		case "excel_create":
		case "csv_create":
		case "script_create":
		case "pptx_create":
			return true;
		default:
			return false;
		}
	}

	private static bool IsTerminalDocumentTool(string toolName)
	{
		switch (toolName)
		{
		case "html_create":
		case "docx_create":
		case "excel_create":
		case "pptx_create":
		case "document_assemble":
		case "csv_create":
			return true;
		default:
			return false;
		}
	}

	private static bool IsCodeVerificationTarget(string toolName)
	{
		switch (toolName)
		{
		case "file_write":
		case "file_edit":
		case "script_create":
		case "process":
			return true;
		default:
			return false;
		}
	}

	private async Task RunPostToolVerificationAsync(List<ChatMessage> messages, string toolName, ToolResult result, AgentContext context, CancellationToken ct)
	{
		EmitEvent(AgentEventType.Thinking, "", "\ud83d\udd0d 생성 결과물 검증 중...", null, 0, 0, null, 0L);
		string filePath = result.FilePath ?? "";
		string fileRef = (string.IsNullOrEmpty(filePath) ? "방금 생성한 결과물" : ("파일 '" + filePath + "'"));
		string checkList = ((ActiveTab == "Code") ? "   - 구문 오류가 없는가?\n   - 참조하는 클래스/메서드/변수가 존재하는가?\n   - 코딩 컨벤션이 일관적인가?\n   - 에지 케이스 처리가 누락되지 않았는가?" : "   - 사용자 요청에 맞는 내용이 모두 포함되었는가?\n   - 구조와 형식이 올바른가?\n   - 누락된 섹션이나 불완전한 내용이 없는가?\n   - 한국어 맞춤법/표현이 자연스러운가?");
		ChatMessage verificationPrompt = new ChatMessage
		{
			Role = "user",
			Content = "[System:Verification] " + fileRef + "을 검증하세요.\n1. file_read 도구로 생성된 파일의 내용을 읽으세요.\n2. 다음 항목을 확인하세요:\n" + checkList + "\n3. 결과를 간단히 보고하세요. 문제가 있으면 구체적으로 무엇이 잘못되었는지 설명하세요.\n⚠\ufe0f 중요: 이 단계에서는 파일을 직접 수정하지 마세요. 보고만 하세요."
		};
		_ = messages.Count;
		messages.Add(verificationPrompt);
		List<ChatMessage> addedMessages = new List<ChatMessage> { verificationPrompt };
		try
		{
			IReadOnlyCollection<IAgentTool> allTools = _tools.GetActiveTools(_settings.Settings.Llm.DisabledTools);
			List<IAgentTool> readOnlyTools = allTools.Where((IAgentTool t) => VerificationAllowedTools.Contains(t.Name)).ToList();
			List<LlmService.ContentBlock> verifyBlocks = await _llm.SendWithToolsAsync(messages, readOnlyTools, ct);
			List<string> verifyText = new List<string>();
			List<LlmService.ContentBlock> verifyToolCalls = new List<LlmService.ContentBlock>();
			foreach (LlmService.ContentBlock block in verifyBlocks)
			{
				if (block.Type == "text" && !string.IsNullOrWhiteSpace(block.Text))
				{
					verifyText.Add(block.Text);
				}
				else if (block.Type == "tool_use")
				{
					verifyToolCalls.Add(block);
				}
			}
			string verifyResponse = string.Join("\n", verifyText);
			if (verifyToolCalls.Count > 0)
			{
				List<object> contentBlocks = new List<object>();
				if (!string.IsNullOrEmpty(verifyResponse))
				{
					contentBlocks.Add(new
					{
						type = "text",
						text = verifyResponse
					});
				}
				foreach (LlmService.ContentBlock tc in verifyToolCalls)
				{
					contentBlocks.Add(new
					{
						type = "tool_use",
						id = tc.ToolId,
						name = tc.ToolName,
						input = tc.ToolInput
					});
				}
				string assistantContent = JsonSerializer.Serialize(new
				{
					_tool_use_blocks = contentBlocks
				});
				ChatMessage assistantMsg = new ChatMessage
				{
					Role = "assistant",
					Content = assistantContent
				};
				messages.Add(assistantMsg);
				addedMessages.Add(assistantMsg);
				foreach (LlmService.ContentBlock tc2 in verifyToolCalls)
				{
					IAgentTool tool = _tools.Get(tc2.ToolName);
					if (tool == null)
					{
						ChatMessage errMsg = LlmService.CreateToolResultMessage(tc2.ToolId, tc2.ToolName, "검증 단계에서는 읽기 도구만 사용 가능합니다.");
						messages.Add(errMsg);
						addedMessages.Add(errMsg);
						continue;
					}
					EmitEvent(AgentEventType.ToolCall, tc2.ToolName, "[검증] " + FormatToolCallSummary(tc2), null, 0, 0, null, 0L);
					try
					{
						JsonElement input = tc2.ToolInput ?? JsonDocument.Parse("{}").RootElement;
						ChatMessage toolMsg = LlmService.CreateToolResultMessage(result: TruncateOutput((await tool.ExecuteAsync(input, context, ct)).Output, 4000), toolId: tc2.ToolId, toolName: tc2.ToolName);
						messages.Add(toolMsg);
						addedMessages.Add(toolMsg);
					}
					catch (Exception ex)
					{
						Exception ex2 = ex;
						ChatMessage errMsg2 = LlmService.CreateToolResultMessage(tc2.ToolId, tc2.ToolName, "검증 도구 실행 오류: " + ex2.Message);
						messages.Add(errMsg2);
						addedMessages.Add(errMsg2);
					}
				}
				verifyResponse = string.Join("\n", from b in await _llm.SendWithToolsAsync(messages, readOnlyTools, ct)
					where b.Type == "text" && !string.IsNullOrWhiteSpace(b.Text)
					select b.Text);
			}
			if (!string.IsNullOrEmpty(verifyResponse))
			{
				string summary = ((verifyResponse.Length > 300) ? (verifyResponse.Substring(0, 300) + "…") : verifyResponse);
				EmitEvent(AgentEventType.Thinking, "", "✅ 검증 결과: " + summary, null, 0, 0, null, 0L);
				if (verifyResponse.Contains("문제") || verifyResponse.Contains("수정") || verifyResponse.Contains("누락") || verifyResponse.Contains("오류") || verifyResponse.Contains("잘못") || verifyResponse.Contains("부족"))
				{
					foreach (ChatMessage msg in addedMessages)
					{
						messages.Remove(msg);
					}
					messages.Add(new ChatMessage
					{
						Role = "user",
						Content = $"[System] 방금 생성한 {fileRef}에 대한 자동 검증 결과, 다음 문제가 발견되었습니다:\n{verifyResponse}\n\n위 문제를 수정해 주세요."
					});
					return;
				}
			}
		}
		catch (Exception ex3)
		{
			EmitEvent(AgentEventType.Error, "", "검증 LLM 호출 실패: " + ex3.Message, null, 0, 0, null, 0L);
		}
		foreach (ChatMessage msg2 in addedMessages)
		{
			messages.Remove(msg2);
		}
	}

	private AgentContext BuildContext()
	{
		LlmSettings llm = _settings.Settings.Llm;
		return new AgentContext
		{
			WorkFolder = llm.WorkFolder,
			Permission = llm.FilePermission,
			BlockedPaths = llm.BlockedPaths,
			BlockedExtensions = llm.BlockedExtensions,
			AskPermission = AskPermissionCallback,
			UserDecision = UserDecisionCallback,
			UserAskCallback = UserAskCallback,
			ToolPermissions = (llm.ToolPermissions ?? new Dictionary<string, string>()),
			ActiveTab = ActiveTab,
			DevMode = llm.DevMode,
			DevModeStepApproval = llm.DevModeStepApproval
		};
	}

	private void EmitEvent(AgentEventType type, string toolName, string summary, string? filePath = null, int stepCurrent = 0, int stepTotal = 0, List<string>? steps = null, long elapsedMs = 0L, int inputTokens = 0, int outputTokens = 0, string? toolInput = null, int iteration = 0)
	{
		string agentLogLevel = _settings.Settings.Llm.AgentLogLevel;
		bool flag = agentLogLevel == "simple";
		bool flag2 = flag;
		if (flag2)
		{
			bool flag3 = (uint)type <= 1u;
			flag2 = flag3;
		}
		if (flag2)
		{
			return;
		}
		if (agentLogLevel == "simple" && summary.Length > 200)
		{
			summary = summary.Substring(0, 200) + "…";
		}
		if (agentLogLevel != "debug")
		{
			toolInput = null;
		}
		AgentEvent evt = new AgentEvent
		{
			Type = type,
			ToolName = toolName,
			Summary = summary,
			FilePath = filePath,
			Success = (type != AgentEventType.Error),
			StepCurrent = stepCurrent,
			StepTotal = stepTotal,
			Steps = steps,
			ElapsedMs = elapsedMs,
			InputTokens = inputTokens,
			OutputTokens = outputTokens,
			ToolInput = toolInput,
			Iteration = iteration
		};
		if (Dispatcher != null)
		{
			Dispatcher(delegate
			{
				Events.Add(evt);
				this.EventOccurred?.Invoke(evt);
			});
		}
		else
		{
			Events.Add(evt);
			this.EventOccurred?.Invoke(evt);
		}
	}

	private string? CheckDecisionRequired(LlmService.ContentBlock call, AgentContext context)
	{
		string text = ((!(Application.Current is App app)) ? null : app.SettingsService?.Settings.Llm.AgentDecisionLevel) ?? "normal";
		string text2 = call.ToolName ?? "";
		JsonElement? toolInput = call.ToolInput;
		if (text2 == "git_tool")
		{
			JsonElement value;
			string text3 = ((toolInput.HasValue && toolInput.GetValueOrDefault().TryGetProperty("action", out value)) ? value.GetString() : "");
			if (text3 == "commit")
			{
				JsonElement value2;
				string text4 = ((toolInput.HasValue && toolInput.GetValueOrDefault().TryGetProperty("args", out value2)) ? value2.GetString() : "");
				return "Git 커밋을 실행하시겠습니까?\n\n커밋 메시지: " + text4;
			}
		}
		if (text == "minimal")
		{
			if (text2 == "process")
			{
				JsonElement value3;
				string text5 = ((toolInput.HasValue && toolInput.GetValueOrDefault().TryGetProperty("command", out value3)) ? value3.GetString() : "");
				return "외부 명령을 실행하시겠습니까?\n\n명령: " + text5;
			}
			return null;
		}
		if (text == "normal" || text == "detailed")
		{
			if (text2 == "process")
			{
				JsonElement value4;
				string text6 = ((toolInput.HasValue && toolInput.GetValueOrDefault().TryGetProperty("command", out value4)) ? value4.GetString() : "");
				return "외부 명령을 실행하시겠습니까?\n\n명령: " + text6;
			}
			if (text2 == "file_write")
			{
				JsonElement value5;
				string text7 = ((toolInput.HasValue && toolInput.GetValueOrDefault().TryGetProperty("file_path", out value5)) ? value5.GetString() : "");
				if (!string.IsNullOrEmpty(text7))
				{
					string path = (Path.IsPathRooted(text7) ? text7 : Path.Combine(context.WorkFolder, text7 ?? ""));
					if (!File.Exists(path))
					{
						return "새 파일을 생성하시겠습니까?\n\n경로: " + text7;
					}
				}
			}
			bool flag;
			switch (text2)
			{
			case "excel_create":
			case "docx_create":
			case "html_create":
			case "csv_create":
			case "script_create":
				flag = true;
				break;
			default:
				flag = false;
				break;
			}
			if (flag)
			{
				JsonElement value6;
				string text8 = ((toolInput.HasValue && toolInput.GetValueOrDefault().TryGetProperty("file_path", out value6)) ? value6.GetString() : "");
				return "문서를 생성하시겠습니까?\n\n도구: " + text2 + "\n경로: " + text8;
			}
			if ((text2 == "build_run" || text2 == "test_loop") ? true : false)
			{
				JsonElement value7;
				string text9 = ((toolInput.HasValue && toolInput.GetValueOrDefault().TryGetProperty("action", out value7)) ? value7.GetString() : "");
				return "빌드/테스트를 실행하시겠습니까?\n\n도구: " + text2 + "\n액션: " + text9;
			}
		}
		if (text == "detailed" && ((text2 == "file_write" || text2 == "file_edit") ? true : false))
		{
			JsonElement value8;
			string text10 = ((toolInput.HasValue && toolInput.GetValueOrDefault().TryGetProperty("file_path", out value8)) ? value8.GetString() : "");
			return "파일을 수정하시겠습니까?\n\n경로: " + text10;
		}
		return null;
	}

	private static string FormatToolCallSummary(LlmService.ContentBlock call)
	{
		if (!call.ToolInput.HasValue)
		{
			return call.ToolName;
		}
		try
		{
			JsonElement value = call.ToolInput.Value;
			if (value.TryGetProperty("path", out var value2))
			{
				return call.ToolName + ": " + value2.GetString();
			}
			if (value.TryGetProperty("command", out var value3))
			{
				return call.ToolName + ": " + value3.GetString();
			}
			if (value.TryGetProperty("pattern", out var value4))
			{
				return call.ToolName + ": " + value4.GetString();
			}
			return call.ToolName;
		}
		catch
		{
			return call.ToolName;
		}
	}

	private static string TruncateOutput(string output, int maxLength)
	{
		if (output.Length <= maxLength)
		{
			return output;
		}
		return output.Substring(0, maxLength) + "\n... (출력 잘림)";
	}

	private static (List<LlmService.ContentBlock> Parallel, List<LlmService.ContentBlock> Sequential) ClassifyToolCalls(List<LlmService.ContentBlock> calls)
	{
		List<LlmService.ContentBlock> list = new List<LlmService.ContentBlock>();
		List<LlmService.ContentBlock> list2 = new List<LlmService.ContentBlock>();
		foreach (LlmService.ContentBlock call in calls)
		{
			if (ReadOnlyTools.Contains(call.ToolName ?? ""))
			{
				list.Add(call);
			}
			else
			{
				list2.Add(call);
			}
		}
		if (list.Count <= 1)
		{
			list2.InsertRange(0, list);
			list.Clear();
		}
		return (Parallel: list, Sequential: list2);
	}

	private async Task ExecuteToolsInParallelAsync(List<LlmService.ContentBlock> calls, List<ChatMessage> messages, AgentContext context, List<string> planSteps, ParallelState state, int baseMax, int maxRetry, LlmSettings llm, int iteration, CancellationToken ct, List<string> statsUsedTools)
	{
		EmitEvent(AgentEventType.Thinking, "", $"읽기 전용 도구 {calls.Count}개를 병렬 실행 중...", null, 0, 0, null, 0L);
		List<Task<(LlmService.ContentBlock call, ToolResult, long)>> tasks = calls.Select(async delegate(LlmService.ContentBlock contentBlock)
		{
			IAgentTool tool = _tools.Get(contentBlock.ToolName);
			if (tool == null)
			{
				return (call: contentBlock, ToolResult.Fail("알 수 없는 도구: " + contentBlock.ToolName), 0L);
			}
			Stopwatch sw = Stopwatch.StartNew();
			try
			{
				JsonElement input = contentBlock.ToolInput ?? JsonDocument.Parse("{}").RootElement;
				ToolResult result2 = await tool.ExecuteAsync(input, context, ct);
				sw.Stop();
				return (call: contentBlock, result2, sw.ElapsedMilliseconds);
			}
			catch (Exception ex)
			{
				sw.Stop();
				return (call: contentBlock, ToolResult.Fail("도구 실행 오류: " + ex.Message), sw.ElapsedMilliseconds);
			}
		}).ToList();
		(LlmService.ContentBlock call, ToolResult, long)[] array = await Task.WhenAll(tasks);
		for (int num = 0; num < array.Length; num++)
		{
			(LlmService.ContentBlock, ToolResult, long) tuple = array[num];
			var (call, result, _) = tuple;
			EmitEvent(elapsedMs: tuple.Item3, type: result.Success ? AgentEventType.ToolResult : AgentEventType.Error, toolName: call.ToolName, summary: TruncateOutput(result.Output, 200), filePath: result.FilePath, stepCurrent: 0, stepTotal: 0, steps: null, inputTokens: 0, outputTokens: 0, toolInput: null, iteration: iteration);
			if (result.Success)
			{
				state.StatsSuccessCount++;
			}
			else
			{
				state.StatsFailCount++;
			}
			if (!statsUsedTools.Contains(call.ToolName))
			{
				statsUsedTools.Add(call.ToolName);
			}
			state.TotalToolCalls++;
			messages.Add(LlmService.CreateToolResultMessage(call.ToolId, call.ToolName, TruncateOutput(result.Output, 4000)));
			if (!result.Success)
			{
				state.ConsecutiveErrors++;
				if (state.ConsecutiveErrors > maxRetry)
				{
					messages.Add(LlmService.CreateToolResultMessage(call.ToolId, call.ToolName, "[FAILED after retries] " + TruncateOutput(result.Output, 500)));
				}
			}
			else
			{
				state.ConsecutiveErrors = 0;
			}
			if (llm.EnableAuditLog)
			{
				AuditLogService.LogToolCall(_conversationId, ActiveTab ?? "", call.ToolName, call.ToolInput?.ToString() ?? "", TruncateOutput(result.Output, 500), result.FilePath, result.Success);
			}
		}
	}
}
