/**
 * SF Command Handlers — fire-and-forget handlers that delegate to other modules.
 *
 * Contains: handleDoctor, handleSteer, handleCapture, handleTriage, handleKnowledge,
 * handleRunHook, handleUpdate, handleSkillHealth
 */
import { existsSync, mkdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { checkRemoteAutoSession, isAutoActive } from "./auto.js";
import { getAutoWorktreePath } from "./auto-worktree.js";
import { appendAutonomousSolverSteering } from "./autonomous-solver.js";
import {
	appendCapture,
	hasPendingCaptures,
	loadPendingCaptures,
} from "./captures.js";
import { projectRoot } from "./commands/context.js";
import { buildTodoTriageLLMCall, triageTodoDump } from "./commands-todo.js";
import {
	filterDoctorIssues,
	formatDoctorIssuesForPrompt,
	formatDoctorReport,
	formatDoctorReportJson,
	runFlowAudit,
	runSFDoctor,
	selectDoctorScope,
} from "./doctor.js";
import { getErrorMessage } from "./error-utils.js";
import { appendKnowledge, appendOverride } from "./files.js";
import { sfRoot } from "./paths.js";
import { loadPrompt } from "./prompt-loader.js";
import { sfHome } from "./sf-home.js";
import { deriveState } from "./state.js";

const UPDATE_REGISTRY_URL =
	"https://registry.npmjs.org/singularity-forge/latest";
const UPDATE_FETCH_TIMEOUT_MS = 5000;
function resolveInstallCommand(pkg) {
	if ("bun" in process.versions) return `bun add -g ${pkg}`;
	return `npm install -g ${pkg}`;
}
async function fetchLatestVersionForCommand() {
	const controller = new AbortController();
	const timeout = setTimeout(() => controller.abort(), UPDATE_FETCH_TIMEOUT_MS);
	try {
		const res = await fetch(UPDATE_REGISTRY_URL, { signal: controller.signal });
		if (!res.ok) return null;
		const data = await res.json();
		const latest =
			typeof data.version === "string"
				? data.version.trim().replace(/^v/, "")
				: "";
		return latest.length > 0 ? latest : null;
	} catch {
		return null;
	} finally {
		clearTimeout(timeout);
	}
}
export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
	const workflowPath =
		process.env.SF_WORKFLOW_PATH ?? join(sfHome(), "agent", "SF-WORKFLOW.md");
	const workflow = existsSync(workflowPath)
		? readFileSync(workflowPath, "utf-8")
		: "";
	const prompt = loadPrompt("doctor-heal", {
		doctorSummary: reportText,
		structuredIssues,
		scopeLabel: scope ?? "active milestone / blocking scope",
		doctorCommandSuffix: scope ? ` ${scope}` : "",
	});
	const content = `Read the following SF workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`;
	pi.sendMessage(
		{ customType: "sf-doctor-heal", content, display: false },
		{ triggerTurn: true },
	);
}
/** Parse doctor command args into structured flags and positionals (pure, no I/O). */
export function parseDoctorArgs(args) {
	const trimmed = args.trim();
	const jsonMode = trimmed.includes("--json");
	const dryRun = trimmed.includes("--dry-run");
	const fixFlag = trimmed.includes("--fix");
	const includeBuild = trimmed.includes("--build");
	const includeTests = trimmed.includes("--test");
	const stripped = trimmed
		.replace(/--json|--dry-run|--build|--test|--fix/g, "")
		.trim();
	const parts = stripped ? stripped.split(/\s+/) : [];
	const mode =
		parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit"
			? parts[0]
			: "doctor";
	const requestedScope = mode === "doctor" ? parts[0] : parts[1];
	return {
		jsonMode,
		dryRun,
		fixFlag,
		includeBuild,
		includeTests,
		mode,
		requestedScope,
	};
}
export function isDoctorHealActionable(issue) {
	return issue.fixable && issue.severity !== "info";
}
export async function handleDoctor(args, ctx, pi) {
	const trimmed = args.trim();
	// ── Flow audit subcommand (sf-moocz9so-4ffov2) ─────────────────────────
	if (trimmed === "flow" || trimmed.startsWith("flow ")) {
		const flowResult = await runFlowAudit(projectRoot(), {
			killOverBudgetChildren: /\b(--kill-children|kill-children|kill)\b/.test(
				trimmed,
			),
		});
		const lines = ["## SF Flow Audit", ""];
		if (flowResult.activeMilestone) {
			lines.push(
				`**Active milestone:** ${flowResult.activeMilestone.id}${flowResult.activeMilestone.title ? ` — ${flowResult.activeMilestone.title}` : ""}`,
				flowResult.activeMilestone.phase
					? `- Phase: ${flowResult.activeMilestone.phase}`
					: "",
				"",
			);
		} else {
			lines.push("**Active milestone:** none", "");
		}
		if (flowResult.activeUnit) {
			const ageMin = Math.round(flowResult.activeUnit.ageMs / 60000);
			const progressAgeMin = Math.round(
				flowResult.activeUnit.progressAgeMs / 60000,
			);
			lines.push(
				`**Active unit:** ${flowResult.activeUnit.unitType} ${flowResult.activeUnit.unitId}`,
				`- Phase: ${flowResult.activeUnit.phase}`,
				`- Started: ${flowResult.activeUnit.startedAt}`,
				`- Age: ${ageMin} minutes`,
				`- Progress age: ${progressAgeMin} minutes`,
				flowResult.activeUnit.lastProgressAt
					? `- Last progress: ${flowResult.activeUnit.lastProgressAt}`
					: "",
				"",
			);
		} else {
			lines.push("**Active unit:** none", "");
		}
		lines.push(
			`**Session pointer:** ${
				flowResult.sessionPointer?.sessionFile ??
				flowResult.sessionPointer?.sessionId ??
				"none recorded"
			}`,
			`**Recommended action:** ${flowResult.recommendedAction}`,
			"",
		);
		if (flowResult.warnings.length > 0) {
			lines.push("**Warnings:**");
			for (const w of flowResult.warnings) lines.push(`- ${w}`);
			lines.push("");
		}
		if (flowResult.staleDispatchedUnits.length > 0) {
			lines.push("**Stale dispatched units:**");
			for (const unit of flowResult.staleDispatchedUnits.slice(0, 5)) {
				lines.push(
					`- ${unit.unitType} ${unit.unitId}: progress age ${Math.round(unit.progressAgeMs / 60000)} minutes`,
				);
			}
			lines.push("");
		}
		if (flowResult.recommendations.length > 0) {
			lines.push("**Recommendations:**");
			for (const r of flowResult.recommendations) lines.push(`- ${r}`);
			lines.push("");
		}
		if (flowResult.childProcesses.length > 0) {
			lines.push("**Child processes:**");
			for (const cp of flowResult.childProcesses.slice(0, 10)) {
				const age =
					cp.ageMs === undefined ? "" : ` age=${Math.round(cp.ageMs / 60000)}m`;
				const nonBlocking = cp.nonBlocking ? " non-blocking" : "";
				lines.push(
					`- pid=${cp.pid} ppid=${cp.ppid} [${cp.classification}]${age}${nonBlocking} action=${cp.action} ${cp.cmd.slice(0, 80)}`,
				);
			}
			lines.push("");
		}
		if (flowResult.runawayHistory.length > 0) {
			lines.push("**Runaway history:**");
			for (const event of flowResult.runawayHistory.slice(-5)) {
				lines.push(`- ${event}`);
			}
			lines.push("");
		}
		if (flowResult.lastErrors.length > 0) {
			lines.push("**Recent errors:**");
			for (const e of flowResult.lastErrors.slice(0, 5)) lines.push(`- ${e}`);
			lines.push("");
		}
		ctx.ui.notify(lines.join("\n"), flowResult.ok ? "info" : "warning");
		return;
	}
	const {
		jsonMode,
		dryRun,
		fixFlag,
		includeBuild,
		includeTests,
		mode,
		requestedScope,
	} = parseDoctorArgs(args);
	const scope = await selectDoctorScope(projectRoot(), requestedScope);
	const effectiveScope =
		mode === "audit" && requestedScope ? requestedScope : scope;
	const report = await runSFDoctor(projectRoot(), {
		fix: mode === "fix" || mode === "heal" || dryRun || fixFlag,
		dryRun,
		scope: effectiveScope,
		includeBuild,
		includeTests,
	});
	if (jsonMode) {
		ctx.ui.notify(formatDoctorReportJson(report), "info");
		return;
	}
	const reportText = formatDoctorReport(report, {
		scope: effectiveScope,
		includeWarnings: mode === "audit",
		maxIssues: mode === "audit" ? 50 : 12,
		title:
			mode === "audit"
				? "SF doctor audit."
				: mode === "heal"
					? "SF doctor heal prep."
					: undefined,
	});
	ctx.ui.notify(reportText, report.ok ? "info" : "warning");
	if (mode === "heal") {
		const unresolved = filterDoctorIssues(report.issues, {
			scope: effectiveScope,
			includeWarnings: true,
		});
		const actionable = unresolved.filter(isDoctorHealActionable);
		if (actionable.length === 0) {
			ctx.ui.notify(
				"Doctor heal found nothing actionable to hand off to the LLM.",
				"info",
			);
			return;
		}
		const structuredIssues = formatDoctorIssuesForPrompt(actionable);
		dispatchDoctorHeal(pi, effectiveScope, reportText, structuredIssues);
		ctx.ui.notify(
			`Doctor heal dispatched ${actionable.length} issue(s) to the LLM.`,
			"info",
		);
	}
}
export async function handleSkillHealth(args, ctx) {
	const {
		generateSkillHealthReport,
		formatSkillHealthReport,
		formatSkillDetail,
	} = await import("./skill-health.js");
	const basePath = projectRoot();
	// /skill-health <skill-name> — detail view
	if (args && !args.startsWith("--")) {
		const detail = formatSkillDetail(basePath, args);
		ctx.ui.notify(detail, "info");
		return;
	}
	// Parse flags
	const staleMatch = args.match(/--stale\s+(\d+)/);
	const staleDays = staleMatch ? parseInt(staleMatch[1], 10) : undefined;
	const decliningOnly = args.includes("--declining");
	const report = generateSkillHealthReport(basePath, staleDays);
	if (decliningOnly) {
		if (report.decliningSkills.length === 0) {
			ctx.ui.notify("No skills flagged for declining performance.", "info");
			return;
		}
		const filtered = {
			...report,
			skills: report.skills.filter((s) => s.flagged),
		};
		ctx.ui.notify(formatSkillHealthReport(filtered), "info");
		return;
	}
	ctx.ui.notify(formatSkillHealthReport(report), "info");
}
export async function handleCapture(args, ctx) {
	// Strip surrounding quotes from the argument
	let text = args.trim();
	if (!text) {
		ctx.ui.notify('Usage: /capture "your thought here"', "warning");
		return;
	}
	// Remove wrapping quotes (single or double)
	if (
		(text.startsWith('"') && text.endsWith('"')) ||
		(text.startsWith("'") && text.endsWith("'"))
	) {
		text = text.slice(1, -1);
	}
	if (!text) {
		ctx.ui.notify('Usage: /capture "your thought here"', "warning");
		return;
	}
	const basePath = process.cwd();
	// Ensure .sf/ exists — capture should work even without a milestone
	const sfDir = sfRoot(basePath);
	if (!existsSync(sfDir)) {
		mkdirSync(sfDir, { recursive: true });
	}
	const id = appendCapture(basePath, text);
	ctx.ui.notify(
		`Captured: ${id} — "${text.length > 60 ? text.slice(0, 57) + "..." : text}"`,
		"info",
	);
}
export async function handleTriage(args, ctx, pi, basePath) {
	const trimmed = args.trim();
	const sourceMatch = trimmed.match(/--source\s+(\S+)/);
	const source = sourceMatch?.[1];
	if (source === "todo") {
		const llmCall = buildTodoTriageLLMCall(ctx);
		if (!llmCall) {
			ctx.ui.notify("No model available for TODO triage.", "warning");
			return;
		}
		try {
			const output = await triageTodoDump(basePath, llmCall, {
				clear: !trimmed.includes("--no-clear"),
				backlog: trimmed.includes("--backlog"),
				ci: trimmed.includes("--ci"),
			});
			if (output.skipped) {
				ctx.ui.notify(
					"TODO.md unchanged since last triage — skipping LLM call.",
					"info",
				);
				return;
			}
			ctx.ui.notify(
				[
					"TODO triage complete.",
					`Report: ${output.markdownPath}`,
					`Normalized inbox: ${output.normalizedJsonlPath}`,
					`Eval candidates: ${output.evalJsonlPath}`,
					`Eval candidate count: ${output.result.eval_candidates.length}`,
					`Backlog items added: ${output.backlogItemsAdded}`,
					output.backlogItemsAdded > 0
						? "TODO.md was reset to the empty dump inbox."
						: "TODO.md was left unchanged.",
				].join("\n"),
				"info",
			);
		} catch (err) {
			ctx.ui.notify(`TODO triage failed: ${getErrorMessage(err)}`, "warning");
		}
		return;
	}
	if (!hasPendingCaptures(basePath)) {
		ctx.ui.notify("No pending captures to triage.", "info");
		return;
	}
	const pending = loadPendingCaptures(basePath);
	ctx.ui.notify(
		`Triaging ${pending.length} pending capture${pending.length === 1 ? "" : "s"}...`,
		"info",
	);
	// Build context for the triage prompt
	const state = await deriveState(basePath);
	let currentPlan = "";
	let roadmapContext = "";
	if (state.activeMilestone && state.activeSlice) {
		const { resolveSliceFile, resolveMilestoneFile } = await import(
			"./paths.js"
		);
		const planFile = resolveSliceFile(
			basePath,
			state.activeMilestone.id,
			state.activeSlice.id,
			"PLAN",
		);
		if (planFile) {
			const { loadFile: load } = await import("./files.js");
			currentPlan = (await load(planFile)) ?? "";
		}
		const roadmapFile = resolveMilestoneFile(
			basePath,
			state.activeMilestone.id,
			"ROADMAP",
		);
		if (roadmapFile) {
			const { loadFile: load } = await import("./files.js");
			roadmapContext = (await load(roadmapFile)) ?? "";
		}
	}
	// Format pending captures for the prompt
	const capturesList = pending
		.map((c) => `- **${c.id}**: "${c.text}" (captured: ${c.timestamp})`)
		.join("\n");
	// Dispatch triage prompt
	const { loadPrompt: loadTriagePrompt } = await import("./prompt-loader.js");
	const prompt = loadTriagePrompt("triage-captures", {
		pendingCaptures: capturesList,
		currentPlan: currentPlan || "(no active slice plan)",
		roadmapContext: roadmapContext || "(no active roadmap)",
	});
	const workflowPath =
		process.env.SF_WORKFLOW_PATH ?? join(sfHome(), "agent", "SF-WORKFLOW.md");
	const workflow = readFileSync(workflowPath, "utf-8");
	pi.sendMessage(
		{
			customType: "sf-triage",
			content: `Read the following SF workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`,
			display: false,
		},
		{ triggerTurn: true },
	);
}
export async function handleSteer(change, ctx, pi) {
	const basePath = process.cwd();
	const trimmed = change.trim();

	// ── Mode steering: /steer mode <workMode> [scope] ──────────────────────
	const modeSteerRe = /^mode\s+(\S+)(?:\s+(\S+))?/;
	const modeMatch = trimmed.match(modeSteerRe);
	if (modeMatch) {
		const workMode = modeMatch[1];
		const scope = modeMatch[2] ?? "after-current-unit";
		const s = getAutoSession();
		const transition = s.setMode({ workMode, reason: "steer", scope });
		ctx.ui.notify(
			`Steer mode: ${transition.from.workMode} → ${transition.to.workMode} (${scope})`,
			"info",
		);
		return;
	}

	// ── Permission-profile steering: /steer permission-profile <profile> [scope]
	const permissionProfileSteerRe = /^permission-profile\s+(\S+)(?:\s+(\S+))?/;
	const permissionProfileMatch = trimmed.match(permissionProfileSteerRe);
	if (permissionProfileMatch) {
		const permissionProfile = permissionProfileMatch[1];
		const scope = permissionProfileMatch[2] ?? "now";
		const s = getAutoSession();
		const transition = s.setMode({ permissionProfile, reason: "steer", scope });
		ctx.ui.notify(
			`Steer permission profile: ${transition.from.permissionProfile} → ${transition.to.permissionProfile} (${scope})`,
			"info",
		);
		return;
	}

	// ── Model-mode steering: /steer model-mode <mode> [scope] ──────────────
	const modelModeSteerRe = /^model-mode\s+(\S+)(?:\s+(\S+))?/;
	const modelModeMatch = trimmed.match(modelModeSteerRe);
	if (modelModeMatch) {
		const modelMode = modelModeMatch[1];
		const scope = modelModeMatch[2] ?? "for-next-unit";
		const s = getAutoSession();
		const transition = s.setMode({ modelMode, reason: "steer", scope });
		ctx.ui.notify(
			`Steer model-mode: ${transition.from.modelMode} → ${transition.to.modelMode} (${scope})`,
			"info",
		);
		return;
	}

	// ── Legacy text override ───────────────────────────────────────────────
	const state = await deriveState(basePath);
	const mid = state.activeMilestone?.id ?? "none";
	const sid = state.activeSlice?.id ?? "none";
	const tid = state.activeTask?.id ?? "none";
	const appliedAt = `${mid}/${sid}/${tid}`;
	// Resolve the correct target path: only route to a worktree when autonomous mode
	// is actively running there (in-process or remote). A worktree directory may
	// exist from a previous session without being the active runtime path —
	// writing there without a live session would silently drop the override.
	const autoRunning =
		isAutoActive() || checkRemoteAutoSession(basePath).running;
	const wtPath =
		autoRunning && mid !== "none" ? getAutoWorktreePath(basePath, mid) : null;
	const targetPath = wtPath ?? basePath;
	await appendOverride(targetPath, change, appliedAt);
	appendAutonomousSolverSteering(targetPath, change, { appliedAt });
	const overrideLoc = wtPath
		? "worktree `.sf/OVERRIDES.md`"
		: "`.sf/OVERRIDES.md`";
	if (autoRunning) {
		ctx.ui.notify(
			`Override registered (${overrideLoc}): "${change}". Will be injected into the next solver iteration.`,
			"info",
		);
	} else {
		pi.sendMessage(
			{
				customType: "sf-hard-steer",
				content: [
					"HARD STEER — User override registered.",
					"",
					`**Override:** ${change}`,
					"",
					`This override has been saved to ${overrideLoc}.`,
					`Before continuing, read ${overrideLoc} and update the current plan documents to reflect this change.`,
					"Focus on: active slice plan, incomplete task plans, and DECISIONS.md.",
				].join("\n"),
				display: false,
			},
			{ triggerTurn: true },
		);
		ctx.ui.notify(
			`Override registered (${overrideLoc}): "${change}". Update plan documents to reflect this change.`,
			"info",
		);
	}
}
export async function handleKnowledge(args, ctx) {
	const parts = args.split(/\s+/);
	const typeArg = parts[0]?.toLowerCase();
	if (!typeArg || !["rule", "pattern", "lesson"].includes(typeArg)) {
		ctx.ui.notify(
			"Usage: /knowledge <rule|pattern|lesson> <description>\nExample: /knowledge rule Use real DB for integration tests",
			"warning",
		);
		return;
	}
	const entryText = parts.slice(1).join(" ").trim();
	if (!entryText) {
		ctx.ui.notify(`Usage: /knowledge ${typeArg} <description>`, "warning");
		return;
	}
	const type = typeArg;
	const basePath = process.cwd();
	const state = await deriveState(basePath);
	const scope = state.activeMilestone?.id
		? `${state.activeMilestone.id}${state.activeSlice ? `/${state.activeSlice.id}` : ""}`
		: "global";
	await appendKnowledge(basePath, type, entryText, scope);
	ctx.ui.notify(`Added ${type} to KNOWLEDGE.md: "${entryText}"`, "success");
}
export async function handleRunHook(args, ctx, pi) {
	const parts = args.trim().split(/\s+/);
	if (parts.length < 3) {
		ctx.ui.notify(
			`Usage: /run-hook <hook-name> <unit-type> <unit-id>

Unit types:
  execute-task   - Task execution (unit-id: M001/S01/T01)
  plan-slice     - Slice planning (unit-id: M001/S01)
  research-milestone - Milestone research (unit-id: M001)
  complete-slice - Slice completion (unit-id: M001/S01)
  complete-milestone - Milestone completion (unit-id: M001)

Examples:
  /run-hook code-review execute-task M001/S01/T01
  /run-hook lint-check plan-slice M001/S01`,
			"warning",
		);
		return;
	}
	const [hookName, unitType, unitId] = parts;
	const basePath = projectRoot();
	// Import the hook trigger function
	const { triggerHookManually, formatHookStatus, getHookStatus } = await import(
		"./post-unit-hooks.js"
	);
	const { dispatchHookUnit } = await import("./auto.js");
	// Check if the hook exists
	const hooks = getHookStatus();
	const hookExists = hooks.some((h) => h.name === hookName);
	if (!hookExists) {
		ctx.ui.notify(
			`Hook "${hookName}" not found. Configured hooks:\n${formatHookStatus()}`,
			"error",
		);
		return;
	}
	// Validate unit ID format
	const unitIdPattern = /^M\d{3}\/S\d{2,3}\/T\d{2,3}$/;
	if (!unitIdPattern.test(unitId)) {
		ctx.ui.notify(
			`Invalid unit ID format: "${unitId}". Expected format: M004/S04/T03`,
			"warning",
		);
		return;
	}
	// Trigger the hook manually
	const hookUnit = triggerHookManually(hookName, unitType, unitId, basePath);
	if (!hookUnit) {
		ctx.ui.notify(
			`Failed to trigger hook "${hookName}". The hook may be disabled or not configured for unit type "${unitType}".`,
			"error",
		);
		return;
	}
	ctx.ui.notify(
		`Manually triggering hook: ${hookName} for ${unitType} ${unitId}`,
		"info",
	);
	// Dispatch the hook unit directly, bypassing normal pre-dispatch hooks
	const success = await dispatchHookUnit(
		ctx,
		pi,
		hookName,
		unitType,
		unitId,
		hookUnit.prompt,
		hookUnit.model,
		basePath,
	);
	if (!success) {
		ctx.ui.notify(
			"Failed to dispatch hook. Autonomous mode may have been cancelled.",
			"error",
		);
	}
}
// ─── Self-update handler ────────────────────────────────────────────────────
function compareSemverLocal(a, b) {
	const pa = a.split(".").map(Number);
	const pb = b.split(".").map(Number);
	for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
		const va = pa[i] || 0;
		const vb = pb[i] || 0;
		if (va > vb) return 1;
		if (va < vb) return -1;
	}
	return 0;
}
export async function handleUpdate(ctx, deps = {}) {
	const { execSync } = await import("node:child_process");
	const NPM_PACKAGE = "singularity-forge";
	const current = deps.currentVersion ?? process.env.SF_VERSION ?? "0.0.0";
	ctx.ui.notify(
		`Current version: v${current}\nChecking npm registry...`,
		"info",
	);
	const latest = await (
		deps.fetchLatestVersion ?? fetchLatestVersionForCommand
	)();
	if (!latest) {
		ctx.ui.notify(
			"Failed to reach npm registry. Check your network connection.",
			"error",
		);
		return;
	}
	if (compareSemverLocal(latest, current) <= 0) {
		ctx.ui.notify(`Already up to date (v${current}).`, "info");
		return;
	}
	ctx.ui.notify(`Updating: v${current} → v${latest}...`, "info");
	const installCmd = resolveInstallCommand(`${NPM_PACKAGE}@latest`);
	try {
		if (deps.install) {
			deps.install(installCmd);
		} else {
			execSync(installCmd, {
				stdio: ["ignore", "pipe", "ignore"],
			});
		}
		ctx.ui.notify(
			`Updated to v${latest}. Reloading current session...`,
			"info",
		);
		try {
			await ctx.reload();
			ctx.ui.notify(`Updated to v${latest}. Reloaded current session.`, "info");
		} catch (reloadError) {
			const message =
				reloadError instanceof Error
					? reloadError.message
					: String(reloadError);
			ctx.ui.notify(
				`Updated to v${latest}, but automatic reload failed: ${message}. Use /reload to resume with the new version.`,
				"warning",
			);
		}
	} catch {
		ctx.ui.notify(`Update failed. Try manually: ${installCmd}`, "error");
	}
}
