import * as path from 'path';
import type { ToolCall } from '../../llm/openai-compat.js';
import { ToolResultCache, type CacheVolatility, type ToolCacheEntry } from '../context/tool-result-cache.js';
import {
  buildReadCacheKey,
  buildGrepCacheKey,
  buildGlobCacheKey,
  buildWebFetchCacheKey,
  buildOfficeCacheKey,
} from '../context/cache-key.js';

// Read が拡張子で内部ディスパッチする office/pdf 系。これらの Read は形式固有の引数
// (sheet/range/query/page_range 等) を持つので OFFICE キー名前空間で args を取り込む。
const OFFICE_READ_EXTENSIONS: ReadonlySet<string> = new Set([
  '.xlsx', '.xls', '.xlsm', '.docx', '.doc', '.pptx', '.ppt', '.pdf',
]);
// .msg は添付保存の副作用があるためキャッシュしない。
const MSG_READ_EXTENSIONS: ReadonlySet<string> = new Set(['.msg']);

export interface CacheRoute {
  cacheKey: string;
  hit: ToolCacheEntry | null;
  displayLabel: string;
  touchedPaths: string[];
  volatility: CacheVolatility;
}

const parseToolArgs = (toolCall: ToolCall): Record<string, unknown> | null => {
  try {
    return JSON.parse(toolCall.function.arguments) as Record<string, unknown>;
  } catch {
    return null;
  }
};

const getString = (args: Record<string, unknown>, key: string): string | undefined => {
  const value = args[key];
  return typeof value === 'string' && value.length > 0 ? value : undefined;
};

const getNumber = (args: Record<string, unknown>, key: string): number | undefined => {
  const value = args[key];
  return typeof value === 'number' ? value : undefined;
};

const describeArgsExcept = (args: Record<string, unknown>, excludeKeys: string[]): string => {
  const keys = Object.keys(args).filter((k) => !excludeKeys.includes(k)).sort();
  if (keys.length === 0) return 'all';
  return keys.map((k) => `${k}=${JSON.stringify(args[k])}`).join('&');
};

const routeRead = (args: Record<string, unknown>, workspacePath: string, cache: ToolResultCache): CacheRoute | null => {
  const filePath = getString(args, 'file_path');
  if (!filePath) return null;
  const ext = path.extname(filePath).toLowerCase();
  // .msg は副作用（添付保存）があるためキャッシュ対象外。
  if (MSG_READ_EXTENSIONS.has(ext)) return null;
  // Office/PDF への内部ディスパッチは形式固有引数を OFFICE キーに取り込む
  // （sheet/range/query/page_range が違えば別エントリになる）。
  if (OFFICE_READ_EXTENSIONS.has(ext)) {
    const range = describeArgsExcept(args, ['file_path']);
    const officeKey = buildOfficeCacheKey({ workspacePath, toolName: 'Read', filePath, range });
    return {
      cacheKey: officeKey,
      hit: cache.get(officeKey) ?? null,
      displayLabel: `Read ${filePath}`,
      touchedPaths: [filePath],
      volatility: 'file',
    };
  }
  const cacheKey = buildReadCacheKey({
    workspacePath,
    filePath,
    offset: getNumber(args, 'offset'),
    limit: getNumber(args, 'limit'),
    byteOffset: getNumber(args, 'byte_offset'),
    byteLength: getNumber(args, 'byte_length'),
  });
  return {
    cacheKey,
    hit: cache.get(cacheKey) ?? null,
    displayLabel: `Read ${filePath}`,
    touchedPaths: [filePath],
    volatility: 'file',
  };
};

const routeGrep = (args: Record<string, unknown>, workspacePath: string, cache: ToolResultCache): CacheRoute | null => {
  const pattern = getString(args, 'pattern');
  if (!pattern) return null;
  const path = getString(args, 'path');
  const glob = getString(args, 'glob');
  const cacheKey = buildGrepCacheKey({ workspacePath, pattern, path, glob });
  return {
    cacheKey,
    hit: cache.get(cacheKey) ?? null,
    displayLabel: `Grep ${pattern}${path ? ` in ${path}` : ''}`,
    touchedPaths: path ? [path] : [],
    volatility: 'search',
  };
};

const routeGlob = (args: Record<string, unknown>, workspacePath: string, cache: ToolResultCache): CacheRoute | null => {
  const pattern = getString(args, 'pattern');
  if (!pattern) return null;
  const path = getString(args, 'path');
  const cacheKey = buildGlobCacheKey({ workspacePath, pattern, path });
  return {
    cacheKey,
    hit: cache.get(cacheKey) ?? null,
    displayLabel: `Glob ${pattern}${path ? ` in ${path}` : ''}`,
    touchedPaths: path ? [path] : [],
    volatility: 'search',
  };
};

const routeWebFetch = (args: Record<string, unknown>, cache: ToolResultCache): CacheRoute | null => {
  const url = getString(args, 'url');
  if (!url) return null;
  const cacheKey = buildWebFetchCacheKey({ url });
  return {
    cacheKey,
    hit: cache.get(cacheKey) ?? null,
    displayLabel: `WebFetch ${url}`,
    touchedPaths: [],
    volatility: 'url',
  };
};

export const routeToolThroughCache = (
  toolCall: ToolCall,
  cache: ToolResultCache | undefined,
  workspacePath: string,
): CacheRoute | null => {
  if (!cache) return null;
  const args = parseToolArgs(toolCall);
  if (!args) return null;

  const name = toolCall.function.name;
  if (name === 'Read') return routeRead(args, workspacePath, cache);
  if (name === 'Grep') return routeGrep(args, workspacePath, cache);
  if (name === 'Glob') return routeGlob(args, workspacePath, cache);
  if (name === 'WebFetch') return routeWebFetch(args, cache);
  return null;
};
