import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";

export type ToolSearchDefaultResult = "updated" | "unchanged" | "skipped-invalid";

export const BUNDLE_TOOL_SEARCH_DEFAULTS = {
  alwaysEnabled: ["codegraph_explore", "lsp_diagnostics"],
  showToolSearchFooterStatus: false,
  maxActiveGroups: 3,
  maxToolsPerGroup: 8,
  maxDynamicTools: 20,
  groupOverrides: {},
} as const;

export interface ToolSearchConfig {
  alwaysEnabled: string[];
  showToolSearchFooterStatus: boolean;
  maxActiveGroups: number;
  maxToolsPerGroup: number;
  maxDynamicTools: number;
  groupOverrides: Record<string, string[]>;
}

function isObject(value: unknown): value is Record<string, unknown> {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}

function positiveInteger(value: unknown, fallback: number): number {
  return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : fallback;
}

function stringList(value: unknown): string[] {
  if (!Array.isArray(value)) return [];
  return [...new Set(value.filter((item): item is string => typeof item === "string" && item.length > 0))];
}

function readGroupOverrides(value: unknown): Record<string, string[]> {
  if (!isObject(value)) return {};
  const overrides: Record<string, string[]> = {};
  for (const [groupId, names] of Object.entries(value)) {
    const tools = stringList(names);
    if (groupId.trim() && tools.length > 0) overrides[groupId] = tools;
  }
  return overrides;
}

export function bundleFallbackConfig(): ToolSearchConfig {
  return {
    alwaysEnabled: [...BUNDLE_TOOL_SEARCH_DEFAULTS.alwaysEnabled],
    showToolSearchFooterStatus: BUNDLE_TOOL_SEARCH_DEFAULTS.showToolSearchFooterStatus,
    maxActiveGroups: BUNDLE_TOOL_SEARCH_DEFAULTS.maxActiveGroups,
    maxToolsPerGroup: BUNDLE_TOOL_SEARCH_DEFAULTS.maxToolsPerGroup,
    maxDynamicTools: BUNDLE_TOOL_SEARCH_DEFAULTS.maxDynamicTools,
    groupOverrides: {},
  };
}

export function readToolSearchConfig(agentDir: string): ToolSearchConfig {
  const fallback = bundleFallbackConfig();
  try {
    const parsed: unknown = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8"));
    if (!isObject(parsed) || !isObject(parsed.toolSearch)) return fallback;
    const config = parsed.toolSearch;
    return {
      alwaysEnabled: Array.isArray(config.alwaysEnabled) ? stringList(config.alwaysEnabled) : fallback.alwaysEnabled,
      showToolSearchFooterStatus:
        typeof config.showToolSearchFooterStatus === "boolean"
          ? config.showToolSearchFooterStatus
          : fallback.showToolSearchFooterStatus,
      maxActiveGroups: positiveInteger(config.maxActiveGroups, fallback.maxActiveGroups),
      maxToolsPerGroup: positiveInteger(config.maxToolsPerGroup, fallback.maxToolsPerGroup),
      maxDynamicTools: positiveInteger(config.maxDynamicTools, fallback.maxDynamicTools),
      groupOverrides: readGroupOverrides(config.groupOverrides),
    };
  } catch {
    return fallback;
  }
}

/** Add bundle defaults without replacing explicit user choices. */
export function ensureToolSearchDefaults(agentDir: string): ToolSearchDefaultResult {
  const targetPath = join(agentDir, "settings.json");
  let settings: Record<string, unknown> = {};

  try {
    const parsed: unknown = JSON.parse(readFileSync(targetPath, "utf8"));
    if (!isObject(parsed)) return "skipped-invalid";
    settings = parsed;
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") return "skipped-invalid";
  }

  const existing = settings.toolSearch;
  if (existing !== undefined && !isObject(existing)) return "skipped-invalid";

  const toolSearch = { ...(existing ?? {}) };
  let changed = false;
  for (const [key, value] of Object.entries(BUNDLE_TOOL_SEARCH_DEFAULTS)) {
    if (Object.hasOwn(toolSearch, key)) continue;
    toolSearch[key] = Array.isArray(value) ? [...value] : isObject(value) ? { ...value } : value;
    changed = true;
  }
  if (!changed) return "unchanged";

  settings.toolSearch = toolSearch;
  mkdirSync(dirname(targetPath), { recursive: true });
  const temporaryPath = `${targetPath}.my-pi.tmp`;
  writeFileSync(temporaryPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
  renameSync(temporaryPath, targetPath);
  return "updated";
}
