export type TerminalStream = 'stdout' | 'stderr' | 'system';
export type TerminalOutput = { stream: TerminalStream; text: string };
export type TerminalRedirection = { path: string; append: boolean };
export type TerminalInvocation = {
  command: string;
  args: readonly string[];
  redirect?: TerminalRedirection;
};
export type TerminalPipeline = readonly TerminalInvocation[];
export type TerminalStatement = { pipeline: TerminalPipeline };

function tokenize(input: string): string[] {
  const out: string[] = [];
  let current = '';
  let quote: '"' | "'" | undefined;
  let escaped = false;
  const push = () => { if (current.length) { out.push(current); current = ''; } };
  for (let i = 0; i < input.length; i += 1) {
    const ch = input[i]!;
    if (escaped) { current += ch; escaped = false; continue; }
    if (ch === '\\' && quote !== "'") { escaped = true; continue; }
    if (quote) { if (ch === quote) quote = undefined; else current += ch; continue; }
    if (ch === '"' || ch === "'") { quote = ch; continue; }
    if (/\s/u.test(ch)) { push(); continue; }
    if (ch === '|' || ch === ';' || ch === '>') {
      push();
      if (ch === '>' && input[i + 1] === '>') { out.push('>>'); i += 1; }
      else out.push(ch);
      continue;
    }
    current += ch;
  }
  if (quote) throw new Error('Unterminated quote');
  if (escaped) current += '\\';
  push();
  return out;
}

export function parseTerminalLine(input: string): TerminalStatement[] {
  const tokens = tokenize(input.trim());
  if (!tokens.length) return [];
  const statements: TerminalStatement[] = [];
  let pipeline: TerminalInvocation[] = [];
  let command: string[] = [];
  let redirect: TerminalRedirection | undefined;
  const pushCommand = () => {
    if (!command.length) throw new Error('Expected command');
    pipeline.push({ command: command[0]!, args: command.slice(1), ...(redirect ? { redirect } : {}) });
    command = [];
    redirect = undefined;
  };
  const pushStatement = () => {
    if (command.length) pushCommand();
    if (pipeline.length) statements.push({ pipeline });
    pipeline = [];
  };
  for (let i = 0; i < tokens.length; i += 1) {
    const token = tokens[i]!;
    if (token === '|' ) { pushCommand(); continue; }
    if (token === ';') { pushStatement(); continue; }
    if (token === '>' || token === '>>') {
      const path = tokens[++i];
      if (!path || path === '|' || path === ';' || path === '>' || path === '>>') throw new Error('Expected redirect path');
      redirect = { path, append: token === '>>' };
      continue;
    }
    command.push(token);
  }
  pushStatement();
  return statements;
}

export function normalizeTerminalPath(cwd: string, input = '.'): string {
  const raw = input.startsWith('/') ? input : `${cwd.replace(/\/$/u, '')}/${input}`;
  const parts: string[] = [];
  for (const segment of raw.split('/')) {
    if (!segment || segment === '.') continue;
    if (segment === '..') { parts.pop(); continue; }
    parts.push(segment);
  }
  const normalized = `/${parts.join('/')}`;
  if (normalized !== '/workspace' && !normalized.startsWith('/workspace/')) {
    throw new Error('Terminal paths are restricted to /workspace');
  }
  return normalized;
}

export type TerminalCapabilityClass =
  | 'read-local'
  | 'compute-local'
  | 'write-local'
  | 'destructive-local'
  | 'network'
  | 'unsupported';

export type TerminalExecutionOrigin = 'user' | 'ai';
export type TerminalAiMode = 'off' | 'copilot' | 'assisted' | 'agent';

export type TerminalPolicyDecision = {
  capability: TerminalCapabilityClass;
  action: 'allow' | 'confirm' | 'deny';
  reason?: string;
};

/**
 * Structured application tools exposed to terminal AI. This registry is
 * intentionally separate from the human shell-like command registry: models
 * never receive an `executeAnything(command)` capability.
 */
export const TERMINAL_TOOL_CAPABILITIES = {
  'terminal.lastExecution': 'read-local',
  'workspace.listFiles': 'read-local',
  'workspace.readFile': 'read-local',
  'workspace.searchFiles': 'read-local',
  'dataset.inspect': 'read-local',
  'scientific.environment': 'read-local',
  'artifact.list': 'read-local',
  'artifact.inspect': 'read-local',
  'workflow.graph': 'read-local',
  'workflow.trace': 'read-local',
  'workflow.changedArtifacts': 'read-local',
  'paperMemory.search': 'read-local',
  'git.status': 'read-local',
  'git.diff': 'read-local',
  'git.history': 'read-local',
  'latex.compile': 'compute-local',
  'scientific.run': 'compute-local',
  'scientific.compileC': 'compute-local',
  'scientific.compileCpp': 'compute-local',
  'scientific.stop': 'compute-local',
  'workflow.rerunArtifact': 'compute-local',
  'scientific.installToolchain': 'network',
  'scientific.installUtility': 'network',
  'scientific.runUtility': 'compute-local',
  'scientific.installPackage': 'network',
  'workspace.writeFile': 'write-local',
  'scientific.createScript': 'write-local',
  'manuscript.proposeResults': 'write-local',
  'workspace.moveFile': 'write-local',
  'workspace.removeFile': 'destructive-local',
  'git.init': 'write-local',
  'git.stage': 'write-local',
  'git.commit': 'write-local',
  'git.branch': 'write-local',
  'git.remote': 'write-local',
  'git.checkout': 'destructive-local',
  'git.merge': 'destructive-local',
  'network.http': 'network',
  'git.fetch': 'network',
  'git.pull': 'network',
  'git.push': 'network',
} as const satisfies Record<string, Exclude<TerminalCapabilityClass, 'unsupported'>>;

export type TerminalToolName = keyof typeof TERMINAL_TOOL_CAPABILITIES;

export function classifyTerminalTool(name: string): TerminalCapabilityClass {
  return TERMINAL_TOOL_CAPABILITIES[name as TerminalToolName] ?? 'unsupported';
}

export function assessTerminalTool(
  name: string,
  origin: TerminalExecutionOrigin = 'ai',
): TerminalPolicyDecision {
  const capability = classifyTerminalTool(name);
  if (capability === 'unsupported') {
    return {
      capability,
      action: 'deny',
      reason: 'AI tool is not registered in Learnitive.',
    };
  }
  if (origin === 'user') {
    return capability === 'destructive-local' || capability === 'network'
      ? { capability, action: 'confirm' }
      : { capability, action: 'allow' };
  }
  return capability === 'read-local'
    ? { capability, action: 'allow' }
    : {
        capability,
        action: 'confirm',
        reason: `${capability} tools require explicit user approval when requested by AI.`,
      };
}

/**
 * Agent-mode policy is deliberately narrower than the human terminal policy.
 * Copilot never executes plan steps. Assisted may auto-run only read-only tools.
 * Agent may auto-run read-only and local-compute tools only after the user has
 * approved the visible plan. Local mutation, destructive and network tools
 * still require a separate approval for that exact step.
 */
export function assessTerminalCapabilityForAiMode(
  capability: TerminalCapabilityClass,
  mode: TerminalAiMode,
  options: { planApproved?: boolean; stepApproved?: boolean } = {},
): TerminalPolicyDecision {
  if (capability === 'unsupported') {
    return { capability, action: 'deny', reason: 'AI tool is not registered in Learnitive.' };
  }
  if (mode === 'off' || mode === 'copilot') {
    return { capability, action: 'deny', reason: 'This Terminal AI mode does not execute agent tools.' };
  }
  if (capability === 'read-local') return { capability, action: 'allow' };
  if (mode === 'agent' && capability === 'compute-local' && options.planApproved) {
    return { capability, action: 'allow' };
  }
  if (options.stepApproved) return { capability, action: 'allow' };
  return {
    capability,
    action: 'confirm',
    reason:
      capability === 'network'
        ? 'AI-originated network access requires approval for this exact step.'
        : capability === 'destructive-local'
          ? 'AI-originated destructive changes require approval for this exact step.'
          : capability === 'write-local'
            ? 'AI-originated project changes require approval for this exact step.'
            : 'This local computation requires approval in Assisted mode.',
  };
}

export function assessTerminalToolForAiMode(
  name: string,
  mode: TerminalAiMode,
  options: { planApproved?: boolean; stepApproved?: boolean } = {},
): TerminalPolicyDecision {
  return assessTerminalCapabilityForAiMode(classifyTerminalTool(name), mode, options);
}

/**
 * Fail-closed registry for the human shell-like terminal surface. Future
 * runtimes add commands here only after their service boundary and policy are
 * implemented; unknown names never inherit a permissive capability.
 */
export const TERMINAL_COMMAND_CAPABILITIES = {
  pwd: 'read-local',
  cd: 'read-local',
  ls: 'read-local',
  cat: 'read-local',
  head: 'read-local',
  tail: 'read-local',
  wc: 'read-local',
  find: 'read-local',
  grep: 'read-local',
  dataset: 'read-local',
  artifact: 'read-local',
  runtime: 'read-local',
  git: 'read-local',
  curl: 'network',
  sed: 'read-local',
  help: 'read-local',
  clear: 'read-local',
  tex: 'compute-local',
  pdflatex: 'compute-local',
  xelatex: 'compute-local',
  lualatex: 'compute-local',
  python: 'compute-local',
  python3: 'compute-local',
  pyodide: 'read-local',
  r: 'compute-local',
  rscript: 'compute-local',
  webr: 'read-local',
  clang: 'compute-local',
  'clang++': 'compute-local',
  cc: 'compute-local',
  'c++': 'compute-local',
  'wasm-run': 'compute-local',
  wasix: 'read-local',
  pandoc: 'compute-local',
  jaq: 'compute-local',
  jq: 'compute-local',
  sqlite3: 'write-local',
  mkdir: 'write-local',
  touch: 'write-local',
  cp: 'write-local',
  mv: 'write-local',
  rm: 'destructive-local',
} as const satisfies Record<string, Exclude<TerminalCapabilityClass, 'unsupported'>>;

export type TerminalRegisteredCommand = keyof typeof TERMINAL_COMMAND_CAPABILITIES;

export function registeredTerminalCommandCapability(command: string): TerminalCapabilityClass {
  return TERMINAL_COMMAND_CAPABILITIES[
    command.toLowerCase() as TerminalRegisteredCommand
  ] ?? 'unsupported';
}

function classifyGitInvocation(args: readonly string[]): TerminalCapabilityClass {
  const subcommand = (args[0] ?? 'status').toLowerCase();
  if (['status', 'diff', 'log', 'branch', 'remote'].includes(subcommand)) {
    if (subcommand === 'branch' && args.some((arg) => ['-d', '-D', '--delete'].includes(arg))) return 'destructive-local';
    if (subcommand === 'branch' && args.some((arg) => !arg.startsWith('-') && arg !== subcommand)) return 'write-local';
    if (subcommand === 'remote' && ['add', 'remove', 'rm'].includes((args[1] ?? '').toLowerCase())) return 'write-local';
    return 'read-local';
  }
  if (['init', 'add', 'stage', 'restore', 'commit', 'config'].includes(subcommand)) return 'write-local';
  if (['checkout', 'switch', 'merge'].includes(subcommand)) return 'destructive-local';
  if (['fetch', 'pull', 'push', 'clone'].includes(subcommand)) return 'network';
  return 'unsupported';
}

export function classifyTerminalLine(input: string): TerminalCapabilityClass {
  let level: TerminalCapabilityClass = 'read-local';
  for (const statement of parseTerminalLine(input)) {
    for (const invocation of statement.pipeline) {
      const command = invocation.command.toLowerCase();
      const registered = registeredTerminalCommandCapability(command);
      if (registered === 'unsupported') return 'unsupported';
      const effective = command === 'git' ? classifyGitInvocation(invocation.args) : registered;
      if (effective === 'unsupported') return 'unsupported';
      if (command === 'webr' && invocation.args[0]?.toLowerCase() === 'install') return 'network';
      if (command === 'wasix' && invocation.args[0]?.toLowerCase() === 'install') return 'network';
      if (command === 'runtime' && ['reset', 'restart'].includes(invocation.args[0]?.toLowerCase() ?? '')) {
        if (level === 'read-local') level = 'compute-local';
      }
      if (effective === 'network') return 'network';
      if (command === 'sed' && invocation.args.includes('-i')) level = 'write-local';
      if (effective === 'destructive-local') return 'destructive-local';
      if (invocation.redirect || effective === 'write-local') level = 'write-local';
      else if (effective === 'compute-local' && level === 'read-local') level = 'compute-local';
    }
  }
  return level;
}

/**
 * Central terminal policy used by UI and future AI tool adapters. Human-entered
 * local read/compute/write commands run directly because the user already
 * expressed intent. Destructive commands still require confirmation. AI
 * execution is deliberately stricter. Structured Agent-mode tools use
 * assessTerminalToolForAiMode(); raw AI terminal lines remain review-only.
 */
export function assessTerminalLine(
  input: string,
  origin: TerminalExecutionOrigin = 'user',
): TerminalPolicyDecision {
  const capability = classifyTerminalLine(input);
  if (capability === 'unsupported') {
    return {
      capability,
      action: 'deny',
      reason: 'Command is not registered in the Learnitive browser terminal.',
    };
  }
  if (origin === 'user') {
    if (capability === 'destructive-local') {
      return { capability, action: 'confirm', reason: 'Destructive local operation.' };
    }
    if (capability === 'network') {
      return { capability, action: 'confirm', reason: 'This command downloads or sends data over the network.' };
    }
    return { capability, action: 'allow' };
  }
  if (capability === 'read-local') return { capability, action: 'allow' };
  return {
    capability,
    action: 'confirm',
    reason:
      capability === 'network'
        ? 'AI-originated network access requires explicit approval.'
        : 'AI-originated computation or mutation requires explicit approval.',
  };
}
