'use strict';

const path = require('node:path');

const AGENT_MODES = Object.freeze(['assistant', 'coding', 'review']);
const CODING_AGENT_KINDS = new Set(['walle-coding', 'coding-agent']);
const ASSISTANT_AGENT_KINDS = new Set(['walle-chat', 'walle-assistant', 'assistant']);

const _loggedWarningKeys = new Set();

function resolveAgentRunContext(input = {}) {
  const opts = input.opts && typeof input.opts === 'object' ? input.opts : input;
  const nestedContext = opts.context && typeof opts.context === 'object' ? opts.context : {};
  const channel = clean(opts.channel || input.channel || 'ctm');
  const explicitAgentModeRaw = clean(opts.agentMode || opts.agent_mode || input.agentMode || input.agent_mode);
  const explicitAgentKindRaw = clean(opts.agentKind || opts.agent_kind || input.agentKind || input.agent_kind);
  const taskTypeRaw = clean(opts.taskType || opts.task_type || input.taskType || input.task_type);

  const explicitAgentMode = explicitAgentModeRaw ? normalizeAgentMode(explicitAgentModeRaw) : '';
  const explicitAgentKind = normalizeAgentKind(explicitAgentKindRaw);
  const explicitCodingSignal = opts.walleCoding === true
    || opts.walle_coding === true
    || explicitAgentMode === 'coding'
    || CODING_AGENT_KINDS.has(explicitAgentKind)
    || isCodingTaskType(taskTypeRaw);
  const explicitAssistantSignal = explicitAgentMode === 'assistant'
    || ASSISTANT_AGENT_KINDS.has(explicitAgentKind);
  const explicitReviewSignal = explicitAgentMode === 'review'
    || isReviewTaskType(taskTypeRaw)
    || opts.codeReviewMode === 'deep'
    || opts.codeReviewMode === 'fast';
  const legacyCodingMode = channel === 'ctm-session'
    && !explicitAgentMode
    && !explicitAgentKind
    && !taskTypeRaw
    && opts.walleCoding !== false
    && opts.legacyCtmCodingMode !== false;

  let agentMode = 'assistant';
  if (explicitAgentMode) agentMode = explicitAgentMode;
  else if (explicitCodingSignal || legacyCodingMode) agentMode = 'coding';
  else if (explicitReviewSignal) agentMode = 'review';
  else if (explicitAssistantSignal) agentMode = 'assistant';

  const agentKind = explicitAgentKind || defaultAgentKind(agentMode);
  const taskType = taskTypeRaw || defaultTaskType(agentMode);
  const sessionId = clean(opts.session_id || opts.sessionId || input.session_id || input.sessionId);
  const chatSessionId = clean(opts.chatSessionId || opts.chat_session_id || nestedContext.chatSessionId || nestedContext.chat_session_id || sessionId);
  const ctmSessionId = clean(opts.ctmSessionId || opts.ctm_session_id || nestedContext.ctmSessionId || nestedContext.ctm_session_id);
  const agentSessionId = clean(opts.agentSessionId || opts.agent_session_id || nestedContext.agentSessionId || nestedContext.agent_session_id || chatSessionId || sessionId);
  const source = clean(opts.source || nestedContext.source || sourceFromChannel(channel));
  const cwd = clean(opts.cwd || nestedContext.cwd || nestedContext.projectPath);
  const jsonlPath = clean(opts.jsonlPath || opts.jsonl_path || nestedContext.jsonlPath || nestedContext.jsonl_path);
  const warnings = buildWarnings({
    channel,
    legacyCodingMode,
    ctmSessionId,
    agentSessionId,
    jsonlPath,
  });

  return {
    version: 1,
    channel,
    source,
    agentMode,
    agentKind,
    taskType,
    isCoding: agentMode === 'coding',
    ctmSessionId,
    agentSessionId,
    chatSessionId,
    sessionId: sessionId || chatSessionId || agentSessionId || ctmSessionId || 'default',
    cwd,
    jsonlPath,
    legacyCodingMode,
    explicit: {
      agentMode: Boolean(explicitAgentModeRaw),
      agentKind: Boolean(explicitAgentKindRaw),
      taskType: Boolean(taskTypeRaw),
      walleCoding: opts.walleCoding === true || opts.walle_coding === true,
    },
    warnings,
  };
}

function normalizeAgentMode(value) {
  const raw = clean(value).toLowerCase();
  if (!raw) return '';
  if (raw === 'chat' || raw === 'default' || raw === 'walle-chat') return 'assistant';
  if (raw === 'code' || raw === 'walle-coding' || raw.startsWith('coding')) return 'coding';
  if (raw === 'code-review' || raw === 'review' || raw === 'reviewer') return 'review';
  return AGENT_MODES.includes(raw) ? raw : '';
}

function normalizeAgentKind(value) {
  return clean(value).toLowerCase().replace(/_/g, '-');
}

function isCodingAgentContext(context = {}) {
  return context.agentMode === 'coding' || CODING_AGENT_KINDS.has(normalizeAgentKind(context.agentKind));
}

function isCodingTaskType(value) {
  const raw = clean(value).toLowerCase();
  return raw === 'coding' || raw.startsWith('coding:') || raw === 'code' || raw === 'implementation';
}

function isReviewTaskType(value) {
  const raw = clean(value).toLowerCase();
  return raw === 'review' || raw === 'code-review' || raw === 'coding:review';
}

function defaultAgentKind(agentMode) {
  if (agentMode === 'coding') return 'walle-coding';
  if (agentMode === 'review') return 'walle-review';
  return 'walle-chat';
}

function defaultTaskType(agentMode) {
  if (agentMode === 'coding') return 'coding';
  if (agentMode === 'review') return 'review';
  return 'chat';
}

function sourceFromChannel(channel) {
  if (channel === 'ctm-session') return 'ctm';
  return channel || 'ctm';
}

function buildWarnings({ channel, legacyCodingMode, ctmSessionId, agentSessionId, jsonlPath }) {
  const warnings = [];
  if (legacyCodingMode) {
    warnings.push({
      code: 'legacy-channel-coding-mode',
      message: 'channel=ctm-session inferred coding mode; callers should pass agentMode or agentKind explicitly',
    });
  }
  if (channel === 'ctm-session' && !ctmSessionId) {
    warnings.push({
      code: 'missing-ctm-session-id',
      message: 'CTM channel did not provide context.ctmSessionId',
    });
  }
  if (channel === 'ctm-session' && ctmSessionId && agentSessionId && ctmSessionId === agentSessionId) {
    warnings.push({
      code: 'ctm-agent-session-id-collapsed',
      message: 'CTM session id and agent session id are identical; mappings should be one CTM session to many agent sessions',
    });
  }
  if (jsonlPath && agentSessionId && !path.basename(jsonlPath).includes(agentSessionId)) {
    warnings.push({
      code: 'jsonl-agent-session-id-mismatch',
      message: 'Wall-E JSONL filename does not include the agent session id',
    });
  }
  return warnings;
}

function emitAgentRunContextWarnings(context = {}, { logger = console, telemetry = null, once = true } = {}) {
  const emitted = [];
  for (const warning of context.warnings || []) {
    const key = `${warning.code}:${context.channel}:${context.agentKind}`;
    if (once && _loggedWarningKeys.has(key)) continue;
    _loggedWarningKeys.add(key);
    emitted.push(warning);
    try {
      logger.warn?.('[walle-runtime]', warning.code, warning.message, {
        channel: context.channel,
        agentMode: context.agentMode,
        agentKind: context.agentKind,
      });
    } catch {}
    try {
      telemetry?.track?.('walle_agent_run_context_warning', {
        code: warning.code,
        channel: context.channel,
        agentMode: context.agentMode,
        agentKind: context.agentKind,
      });
    } catch {}
  }
  return emitted;
}

function clean(value) {
  return typeof value === 'string' ? value.trim() : '';
}

module.exports = {
  AGENT_MODES,
  resolveAgentRunContext,
  emitAgentRunContextWarnings,
  isCodingAgentContext,
  normalizeAgentMode,
};
