import chalk from "chalk";
import { spawn } from "child_process";
import { parseCodexBaseURL } from "@dexto/core";
import { formatForInkCli } from "./utils/format-output.js";
import { CommandOutputHelper } from "./utils/command-output.js";
import { writeToClipboard } from "../utils/clipboardUtils.js";
import { hasMeaningfulTokenUsage, setExitStats } from "./exit-stats.js";
import { triggerExit } from "./exit-handler.js";
function getShellRcFile(shell) {
  const home = process.env.HOME;
  if (!home) return null;
  if (shell.includes("zsh")) {
    return `${home}/.zshrc`;
  } else if (shell.includes("bash")) {
    return `${home}/.bashrc`;
  }
  return null;
}
function wrapCommandWithRcSource(command, shell) {
  const rcFile = getShellRcFile(shell);
  if (!rcFile) {
    return command;
  }
  const escapedCommand = command.replace(/'/g, "'\\''");
  if (shell.includes("bash")) {
    return `source "${rcFile}" 2>/dev/null; shopt -s expand_aliases 2>/dev/null; eval '${escapedCommand}'`;
  }
  return `source "${rcFile}" 2>/dev/null; eval '${escapedCommand}'`;
}
function isChatGPTLoginConfig(agent, sessionId) {
  const llmConfig = agent.getCurrentLLMConfig(sessionId);
  return llmConfig.provider === "openai-compatible" && parseCodexBaseURL(llmConfig.baseURL)?.authMode === "chatgpt";
}
async function executeShellCommand(command, cwd, timeoutMs = 3e4) {
  return new Promise((resolve) => {
    const userShell = process.env.SHELL || "/bin/sh";
    const wrappedCommand = wrapCommandWithRcSource(command, userShell);
    const child = spawn(userShell, ["-c", wrappedCommand], {
      cwd,
      stdio: ["ignore", "pipe", "pipe"],
      env: { ...process.env },
      detached: true
    });
    let stdout = "";
    let stderr = "";
    const timer = setTimeout(() => {
      child.kill();
      resolve({ stdout, stderr: `Command timed out after ${timeoutMs}ms`, exitCode: -1 });
    }, timeoutMs);
    child.stdout.on("data", (data) => {
      stdout += data.toString();
    });
    child.stderr.on("data", (data) => {
      stderr += data.toString();
    });
    child.on("error", (error) => {
      clearTimeout(timer);
      resolve({ stdout, stderr: error.message, exitCode: -1 });
    });
    child.on("close", (code) => {
      clearTimeout(timer);
      resolve({ stdout, stderr, exitCode: code ?? -1 });
    });
  });
}
function createHelpCommand(getAvailableCommandsForAgent) {
  return {
    name: "help",
    description: "Show help information",
    usage: "/help",
    category: "General",
    aliases: ["h", "?"],
    handler: async (_args, agent, _ctx) => {
      const allCommands = getAvailableCommandsForAgent(agent);
      const styledData = {
        commands: allCommands.map((cmd) => ({
          name: cmd.name,
          description: cmd.description,
          category: cmd.category || "General"
        }))
      };
      const fallbackLines = ["Available Commands:"];
      for (const cmd of allCommands) {
        fallbackLines.push(`  /${cmd.name} - ${cmd.description}`);
      }
      return CommandOutputHelper.styled("help", styledData, fallbackLines.join("\n"));
    }
  };
}
const generalCommands = [
  {
    name: "shell",
    description: "Execute shell command directly (use !command as shortcut)",
    usage: "!<command> or /shell <command>",
    category: "General",
    handler: async (args, agent, _ctx) => {
      const command = args.join(" ").trim();
      if (!command) {
        return formatForInkCli("\u274C No command provided. Usage: !<command>");
      }
      const cwd = process.cwd();
      agent.logger.debug(`Executing shell command: ${command}`);
      const { stdout, stderr, exitCode } = await executeShellCommand(command, cwd);
      const lines = [];
      if (stdout.trim()) {
        lines.push(stdout.trim());
      }
      if (stderr.trim()) {
        lines.push(chalk.yellow(stderr.trim()));
      }
      if (exitCode !== 0) {
        lines.push(chalk.red(`Exit code: ${exitCode}`));
      }
      if (lines.length === 0) {
        return formatForInkCli(chalk.gray("(no output)"));
      }
      return formatForInkCli(lines.join("\n"));
    }
  },
  {
    name: "exit",
    description: "Exit the CLI",
    usage: "/exit",
    category: "General",
    aliases: ["quit", "q"],
    handler: async (_args, agent, ctx) => {
      try {
        const { sessionId } = ctx;
        if (sessionId) {
          const [sessionMetadata, history] = await Promise.all([
            agent.sessionManager.getSessionMetadata(sessionId),
            agent.getSessionHistory(sessionId)
          ]);
          if (sessionMetadata) {
            const usedChatGPTLogin = sessionMetadata.usageTracking?.hasUntrackedChatGPTLoginUsage || isChatGPTLoginConfig(agent, sessionId);
            const hasTrackedTokenUsage = hasMeaningfulTokenUsage(
              sessionMetadata.tokenUsage
            );
            const usageNote = usedChatGPTLogin && hasTrackedTokenUsage ? "Partial totals only. This session used ChatGPT Login and other tracked models; ChatGPT token counts are not available in Dexto." : usedChatGPTLogin ? "Tracked via ChatGPT Login. Token counts are not available in Dexto for this session." : void 0;
            let durationStr;
            if (sessionMetadata.createdAt) {
              const duration = Date.now() - new Date(sessionMetadata.createdAt).getTime();
              const minutes = Math.floor(duration / 6e4);
              const seconds = Math.floor(duration % 6e4 / 1e3);
              durationStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
            }
            const messageCount = {
              total: history?.length || 0,
              user: history?.filter((msg) => msg.role === "user").length || 0,
              assistant: history?.filter((msg) => msg.role === "assistant").length || 0
            };
            setExitStats({
              ...sessionId && { sessionId },
              ...durationStr && { duration: durationStr },
              messageCount,
              ...sessionMetadata.tokenUsage && hasTrackedTokenUsage && {
                tokenUsage: sessionMetadata.tokenUsage
              },
              ...sessionMetadata.estimatedCost !== void 0 && {
                estimatedCost: sessionMetadata.estimatedCost
              },
              ...sessionMetadata.modelStats && {
                modelStats: sessionMetadata.modelStats
              },
              ...usageNote && { usageNote }
            });
          }
        }
      } catch (error) {
        agent.logger.debug(
          `Failed to collect session stats on exit: ${error instanceof Error ? error.message : String(error)}`
        );
      }
      triggerExit();
      return true;
    }
  },
  {
    name: "new",
    description: "Start a new conversation",
    usage: "/new",
    category: "General",
    handler: async (_args, agent, _ctx) => {
      try {
        const newSession = await agent.createSession();
        const newSessionId = newSession.id;
        agent.emit("session:created", {
          sessionId: newSessionId,
          switchTo: true
        });
        return formatForInkCli(
          `\u2728 New conversation started
\u{1F4A1} Use /resume to see previous conversations`
        );
      } catch (error) {
        const errorMsg = `Failed to create new session: ${error instanceof Error ? error.message : String(error)}`;
        agent.logger.error(errorMsg);
        return formatForInkCli(`\u274C ${errorMsg}`);
      }
    }
  },
  {
    name: "clear",
    description: "Continue conversation, free up AI context window",
    usage: "/clear",
    category: "General",
    handler: async (_args, agent, ctx) => {
      try {
        const { sessionId } = ctx;
        if (!sessionId) {
          return formatForInkCli("\u26A0\uFE0F  No active session to clear");
        }
        await agent.clearContext(sessionId);
        return formatForInkCli(
          "\u{1F9F9} Context window cleared\n\u{1F4A1} Conversation continues - AI will not see older messages"
        );
      } catch (error) {
        const errorMsg = `Failed to clear context: ${error instanceof Error ? error.message : String(error)}`;
        agent.logger.error(errorMsg);
        return formatForInkCli(`\u274C ${errorMsg}`);
      }
    }
  },
  {
    name: "compact",
    description: "Compact context by summarizing older messages",
    usage: "/compact",
    category: "General",
    aliases: ["summarize"],
    handler: async (_args, agent, ctx) => {
      try {
        const { sessionId } = ctx;
        if (!sessionId) {
          return formatForInkCli("\u26A0\uFE0F  No active session to compact");
        }
        const result = await agent.compactContext(sessionId);
        if (!result) {
          return formatForInkCli(
            "\u{1F4A1} Nothing to compact - history is too short or compaction is not configured."
          );
        }
        return true;
      } catch (error) {
        const errorMsg = `Failed to compact context: ${error instanceof Error ? error.message : String(error)}`;
        agent.logger.error(errorMsg);
        return formatForInkCli(`\u274C ${errorMsg}`);
      }
    }
  },
  {
    name: "context",
    description: "Show context window usage statistics",
    usage: "/context",
    category: "General",
    aliases: ["ctx", "tokens"],
    handler: async (_args, agent, ctx) => {
      try {
        const { sessionId } = ctx;
        if (!sessionId) {
          return formatForInkCli("\u26A0\uFE0F  No active session");
        }
        const stats = await agent.getContextStats(sessionId);
        const barWidth = 20;
        const displayPercent = Math.min(Math.max(stats.usagePercent, 0), 100);
        const filledWidth = Math.round(displayPercent / 100 * barWidth);
        const emptyWidth = barWidth - filledWidth;
        const progressBar = "\u2588".repeat(filledWidth) + "\u2591".repeat(emptyWidth);
        let usageColor = chalk.green;
        if (stats.usagePercent > 80) usageColor = chalk.red;
        else if (stats.usagePercent > 60) usageColor = chalk.yellow;
        const formatTokens = (tokens) => {
          if (tokens >= 1e3) {
            return `${(tokens / 1e3).toFixed(1)}k`;
          }
          return tokens.toLocaleString();
        };
        const autoCompactBuffer = stats.thresholdPercent > 0 && stats.thresholdPercent < 1 ? Math.floor(
          stats.maxContextTokens * (1 - stats.thresholdPercent) / stats.thresholdPercent
        ) : 0;
        const bufferPercent = Math.round((1 - stats.thresholdPercent) * 100);
        const bufferLabel = bufferPercent > 0 ? `Auto compact buffer (${bufferPercent}%)` : "Auto compact buffer";
        const totalTokenSpace = stats.maxContextTokens + autoCompactBuffer;
        const usedTokens = stats.estimatedTokens + autoCompactBuffer;
        const pct = (tokens) => {
          const percent = totalTokenSpace > 0 ? (tokens / totalTokenSpace * 100).toFixed(1) : "0.0";
          return `${percent}%`;
        };
        const overflowWarning = stats.usagePercent > 100 ? " \u26A0\uFE0F  OVERFLOW" : "";
        const { breakdown } = stats;
        const tokenDisplay = `~${formatTokens(usedTokens)}`;
        const breakdownLabel = chalk.dim("(estimated)");
        const lines = [
          `\u{1F4CA} Context Usage`,
          `   ${usageColor(progressBar)} ${stats.usagePercent}%${overflowWarning}`,
          `   ${chalk.dim(stats.modelDisplayName)} \xB7 ${tokenDisplay} / ${formatTokens(totalTokenSpace)} tokens`,
          ``,
          `   ${chalk.cyan("Breakdown:")} ${breakdownLabel}`,
          `   \u251C\u2500 System prompt: ${formatTokens(breakdown.systemPrompt)} (${pct(breakdown.systemPrompt)})`,
          `   \u251C\u2500 Tools: ${formatTokens(breakdown.tools.total)} (${pct(breakdown.tools.total)})`,
          `   \u251C\u2500 Messages: ${formatTokens(breakdown.messages)} (${pct(breakdown.messages)})`,
          `   \u2514\u2500 ${bufferLabel}: ${formatTokens(autoCompactBuffer)} (${pct(autoCompactBuffer)})`,
          ``,
          `   Messages: ${stats.filteredMessageCount} visible (${stats.messageCount} total)`
        ];
        if (stats.prunedToolCount > 0) {
          lines.push(`   \u{1F5D1}\uFE0F  ${stats.prunedToolCount} tool output(s) pruned`);
        }
        if (stats.hasSummary) {
          lines.push(`   \u{1F4E6} Context has been compacted`);
        }
        if (stats.usagePercent > 100) {
          lines.push(
            `   \u{1F4A1} Use /compact to manually compact, or send a message to trigger auto-compaction`
          );
        }
        return formatForInkCli(lines.join("\n"));
      } catch (error) {
        const errorMsg = `Failed to get context stats: ${error instanceof Error ? error.message : String(error)}`;
        agent.logger.error(errorMsg);
        return formatForInkCli(`\u274C ${errorMsg}`);
      }
    }
  },
  {
    name: "copy",
    description: "Copy the last assistant response to clipboard",
    usage: "/copy",
    category: "General",
    aliases: ["cp"],
    handler: async (_args, agent, ctx) => {
      try {
        const { sessionId } = ctx;
        if (!sessionId) {
          return formatForInkCli("\u274C No active session");
        }
        const history = await agent.getSessionHistory(sessionId);
        if (!history || history.length === 0) {
          return formatForInkCli("\u274C No messages in current session");
        }
        const lastAssistantMessage = [...history].reverse().find((msg) => msg.role === "assistant");
        if (!lastAssistantMessage) {
          return formatForInkCli("\u274C No assistant response to copy");
        }
        let textContent = "";
        if (typeof lastAssistantMessage.content === "string") {
          textContent = lastAssistantMessage.content;
        } else if (Array.isArray(lastAssistantMessage.content)) {
          textContent = lastAssistantMessage.content.filter(
            (part) => part.type === "text"
          ).map((part) => part.text).join("\n");
        }
        if (!textContent) {
          return formatForInkCli("\u274C No text content to copy");
        }
        const success = await writeToClipboard(textContent);
        if (success) {
          const preview = textContent.length > 50 ? textContent.substring(0, 50) + "..." : textContent;
          return formatForInkCli(
            `\u{1F4CB} Copied to clipboard (${textContent.length} chars)
${preview.replace(/\n/g, " ")}`
          );
        } else {
          return formatForInkCli("\u274C Failed to copy to clipboard");
        }
      } catch (error) {
        const errorMsg = `Failed to copy: ${error instanceof Error ? error.message : String(error)}`;
        agent.logger.error(errorMsg);
        return formatForInkCli(`\u274C ${errorMsg}`);
      }
    }
  },
  {
    name: "shortcuts",
    description: "Show keyboard shortcuts",
    usage: "/shortcuts",
    category: "General",
    aliases: ["keys", "hotkeys"],
    handler: async (_args, _agent, _ctx) => {
      const styledData = {
        categories: [
          {
            name: "Global",
            shortcuts: [
              { keys: "Ctrl+C", description: "Clear input, then exit (press twice)" },
              { keys: "Ctrl+T", description: "Toggle todo list (show/hide todos)" },
              { keys: "Escape", description: "Cancel processing / close overlay" },
              {
                keys: "Shift+Tab",
                description: "Cycle modes (normal \u2192 plan mode \u2192 accept edits \u2192 bypass permissions)"
              }
            ]
          },
          {
            name: "Input",
            shortcuts: [
              { keys: "Enter", description: "Submit message" },
              { keys: "Shift+Enter", description: "New line (multi-line input)" },
              { keys: "Up/Down", description: "Navigate input history" },
              { keys: "Ctrl+R", description: "Search history (enter search mode)" },
              {
                keys: "Tab",
                description: "Cycle reasoning variant (when no overlay is open)"
              },
              { keys: "Ctrl+U", description: "Clear input line" },
              { keys: "Ctrl+W", description: "Delete word before cursor" },
              { keys: "Ctrl+A", description: "Move cursor to start" },
              { keys: "Ctrl+E", description: "Move cursor to end" }
            ]
          },
          {
            name: "History Search (after Ctrl+R)",
            shortcuts: [
              { keys: "Ctrl+R", description: "Next older match" },
              { keys: "Ctrl+E", description: "Next newer match" },
              { keys: "Enter", description: "Accept and exit search" },
              { keys: "Escape", description: "Cancel search" }
            ]
          },
          {
            name: "Autocomplete & Selectors",
            shortcuts: [
              { keys: "Up/Down", description: "Navigate options" },
              { keys: "Enter", description: "Select / execute" },
              { keys: "Tab", description: "Load command into input" },
              { keys: "Escape", description: "Close overlay" }
            ]
          },
          {
            name: "Tool Approval",
            shortcuts: [
              { keys: "y", description: "Allow once" },
              { keys: "a", description: "Allow for session" },
              { keys: "n", description: "Deny" },
              { keys: "Escape", description: "Cancel" }
            ]
          }
        ]
      };
      const fallbackLines = ["Keyboard Shortcuts:"];
      for (const category of styledData.categories) {
        fallbackLines.push(`
${category.name}:`);
        for (const shortcut of category.shortcuts) {
          fallbackLines.push(`  ${shortcut.keys.padEnd(14)} ${shortcut.description}`);
        }
      }
      return CommandOutputHelper.styled("shortcuts", styledData, fallbackLines.join("\n"));
    }
  }
];
export {
  createHelpCommand,
  generalCommands
};
