/**
 * AI Assistant Tool definitions for OpenAI Function Calling.
 * Each tool has a JSON schema (for the AI) and an execute function (for the server).
 */

import { prisma, Prisma, ImportBatchStatus, StagedRecordStatus, DispoStagedRecordType, VacationType } from "@capakraken/db";
import {
  CreateAssignmentSchema,
  CreateClientSchema,
  CreateCountrySchema,
  type CreateEstimateInput,
  CreateHolidayCalendarEntrySchema,
  CreateHolidayCalendarSchema,
  CreateProjectSchema,
  CreateResourceSchema,
  CreateMetroCitySchema,
  CreateOrgUnitSchema,
  CreateRoleSchema,
  AllocationStatus,
  EstimateExportFormat,
  EstimateStatus,
  PermissionKey,
  PreviewResolvedHolidaysSchema,
  SystemRole,
  UpdateClientSchema,
  type UpdateEstimateDraftInput,
  UpdateCountrySchema,
  UpdateHolidayCalendarEntrySchema,
  UpdateHolidayCalendarSchema,
  UpdateAssignmentSchema,
  UpdateMetroCitySchema,
  UpdateOrgUnitSchema,
  UpdateProjectSchema,
  UpdateRoleSchema,
  UpdateResourceSchema,
} from "@capakraken/shared";
import type { WeekdayAvailability } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { fmtEur } from "../lib/format-utils.js";
import { timelineRouter } from "./timeline.js";
import { logger } from "../lib/logger.js";
import { createCallerFactory, type TRPCContext } from "../trpc.js";
import { auditLogRouter } from "./audit-log.js";
import { chargeabilityReportRouter } from "./chargeability-report.js";
import { computationGraphRouter } from "./computation-graph.js";
import { dispoRouter } from "./dispo.js";
import { importExportRouter } from "./import-export.js";
import { resourceRouter } from "./resource.js";
import { settingsRouter } from "./settings.js";
import { systemRoleConfigRouter } from "./system-role-config.js";
import { userRouter } from "./user.js";
import { notificationRouter } from "./notification.js";
import { estimateRouter } from "./estimate.js";
import { webhookRouter } from "./webhook.js";
import { countryRouter } from "./country.js";
import { holidayCalendarRouter } from "./holiday-calendar.js";
import { blueprintRouter } from "./blueprint.js";
import { roleRouter } from "./role.js";
import { clientRouter } from "./client.js";
import { orgUnitRouter } from "./org-unit.js";
import { projectRouter } from "./project.js";
import { rateCardRouter } from "./rate-card.js";
import { reportRouter } from "./report.js";
import { vacationRouter } from "./vacation.js";
import { entitlementRouter } from "./entitlement.js";
import { commentRouter } from "./comment.js";
import { managementLevelRouter } from "./management-level.js";
import { utilizationCategoryRouter } from "./utilization-category.js";
import { calculationRuleRouter } from "./calculation-rules.js";
import { effortRuleRouter } from "./effort-rule.js";
import { experienceMultiplierRouter } from "./experience-multiplier.js";
import { dashboardRouter } from "./dashboard.js";
import { insightsRouter } from "./insights.js";
import { scenarioRouter } from "./scenario.js";
import { allocationRouter } from "./allocation.js";
import { staffingRouter } from "./staffing.js";

// ─── Mutation tool set for audit logging (EGAI 4.1.3.1 / IAAI 3.6.26) ──────

export const MUTATION_TOOLS = new Set([
  "import_csv_data",
  "update_system_settings",
  "test_ai_connection",
  "test_smtp_connection",
  "test_gemini_connection",
  "update_system_role_config",
  "create_webhook",
  "update_webhook",
  "delete_webhook",
  "test_webhook",
  "stage_dispo_import_batch",
  "cancel_dispo_import_batch",
  "resolve_dispo_staged_record",
  "commit_dispo_import_batch",
  "create_allocation", "cancel_allocation", "update_allocation_status",
  "update_timeline_allocation_inline", "apply_timeline_project_shift",
  "quick_assign_timeline_resource", "batch_quick_assign_timeline_resources",
  "batch_shift_timeline_allocations",
  "update_resource", "deactivate_resource", "create_resource",
  "update_project", "create_project", "delete_project",
  "create_vacation", "approve_vacation", "reject_vacation", "cancel_vacation",
  "set_entitlement", "create_demand", "fill_demand",
  "generate_project_cover", "remove_project_cover",
  "create_role", "update_role", "delete_role",
  "create_client", "update_client",
  "create_org_unit", "update_org_unit",
  "create_country", "update_country",
  "create_metro_city", "update_metro_city", "delete_metro_city",
  "create_holiday_calendar", "update_holiday_calendar", "delete_holiday_calendar",
  "create_holiday_calendar_entry", "update_holiday_calendar_entry", "delete_holiday_calendar_entry",
  "send_broadcast", "create_task_for_user", "create_reminder",
  "update_task_status", "execute_task_action",
  "create_comment", "resolve_comment", "mark_notification_read",
  "save_dashboard_layout", "toggle_favorite_project",
  "set_column_preferences", "generate_totp_secret", "verify_and_enable_totp",
  "create_user", "set_user_password", "update_user_role", "update_user_name",
  "link_user_resource", "auto_link_users_by_email", "set_user_permissions",
  "reset_user_permissions", "disable_user_totp",
  "create_notification", "update_reminder", "delete_reminder",
  "delete_notification", "assign_task",
  "clone_estimate", "update_estimate_draft", "submit_estimate_version",
  "approve_estimate_version", "create_estimate_revision",
  "create_estimate_export", "create_estimate_planning_handoff",
  "generate_estimate_weekly_phasing", "update_estimate_commercial_terms",
]);

export const ADVANCED_ASSISTANT_TOOLS = new Set([
  "find_best_project_resource",
  "get_timeline_entries_view",
  "get_timeline_holiday_overlays",
  "get_project_timeline_context",
  "preview_project_shift",
  "update_timeline_allocation_inline",
  "apply_timeline_project_shift",
  "quick_assign_timeline_resource",
  "batch_quick_assign_timeline_resources",
  "batch_shift_timeline_allocations",
  "get_chargeability_report",
  "get_resource_computation_graph",
  "get_project_computation_graph",
]);

// ─── Types ──────────────────────────────────────────────────────────────────

export type ToolContext = {
  db: typeof prisma;
  userId: string;
  userRole: string;
  permissions: Set<PermissionKey>;
  session?: TRPCContext["session"];
  dbUser?: TRPCContext["dbUser"];
  roleDefaults?: TRPCContext["roleDefaults"];
};

export interface ToolDef {
  type: "function";
  function: {
    name: string;
    description: string;
    parameters: Record<string, unknown>;
  };
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ToolExecutor = (params: any, ctx: ToolContext) => Promise<unknown>;

const createChargeabilityReportCaller = createCallerFactory(chargeabilityReportRouter);
const createComputationGraphCaller = createCallerFactory(computationGraphRouter);
const createTimelineCaller = createCallerFactory(timelineRouter);
const createAuditLogCaller = createCallerFactory(auditLogRouter);
const createImportExportCaller = createCallerFactory(importExportRouter);
const createDispoCaller = createCallerFactory(dispoRouter);
const createResourceCaller = createCallerFactory(resourceRouter);
const createSettingsCaller = createCallerFactory(settingsRouter);
const createSystemRoleConfigCaller = createCallerFactory(systemRoleConfigRouter);
const createUserCaller = createCallerFactory(userRouter);
const createNotificationCaller = createCallerFactory(notificationRouter);
const createEstimateCaller = createCallerFactory(estimateRouter);
const createWebhookCaller = createCallerFactory(webhookRouter);
const createCountryCaller = createCallerFactory(countryRouter);
const createHolidayCalendarCaller = createCallerFactory(holidayCalendarRouter);
const createBlueprintCaller = createCallerFactory(blueprintRouter);
const createRoleCaller = createCallerFactory(roleRouter);
const createClientCaller = createCallerFactory(clientRouter);
const createOrgUnitCaller = createCallerFactory(orgUnitRouter);
const createProjectCaller = createCallerFactory(projectRouter);
const createRateCardCaller = createCallerFactory(rateCardRouter);
const createReportCaller = createCallerFactory(reportRouter);
const createVacationCaller = createCallerFactory(vacationRouter);
const createEntitlementCaller = createCallerFactory(entitlementRouter);
const createCommentCaller = createCallerFactory(commentRouter);
const createManagementLevelCaller = createCallerFactory(managementLevelRouter);
const createUtilizationCategoryCaller = createCallerFactory(utilizationCategoryRouter);
const createCalculationRuleCaller = createCallerFactory(calculationRuleRouter);
const createEffortRuleCaller = createCallerFactory(effortRuleRouter);
const createExperienceMultiplierCaller = createCallerFactory(experienceMultiplierRouter);
const createDashboardCaller = createCallerFactory(dashboardRouter);
const createInsightsCaller = createCallerFactory(insightsRouter);
const createScenarioCaller = createCallerFactory(scenarioRouter);
const createAllocationCaller = createCallerFactory(allocationRouter);
const createStaffingCaller = createCallerFactory(staffingRouter);

// ─── Helpers ────────────────────────────────────────────────────────────────

function fmtDate(d: Date | null | undefined): string | null {
  return d ? d.toISOString().slice(0, 10) : null;
}

function assertPermission(ctx: ToolContext, perm: PermissionKey): void {
  if (!ctx.permissions.has(perm)) {
    throw new Error(`Permission denied: you need the "${perm}" permission to perform this action.`);
  }
}

function assertAdminRole(ctx: ToolContext): void {
  if (ctx.userRole !== SystemRole.ADMIN) {
    throw new Error("Admin role required to perform this action.");
  }
}

function formatHolidayCalendarEntry(entry: {
  id: string;
  date: Date;
  name: string;
  isRecurringAnnual?: boolean | null;
  source?: string | null;
}) {
  return {
    id: entry.id,
    date: fmtDate(entry.date),
    name: entry.name,
    isRecurringAnnual: entry.isRecurringAnnual ?? false,
    source: entry.source ?? null,
  };
}

function formatHolidayCalendar(calendar: {
  id: string;
  name: string;
  scopeType: string;
  stateCode?: string | null;
  isActive?: boolean | null;
  priority?: number | null;
  country?: { id: string; code: string; name: string } | null;
  metroCity?: { id: string; name: string } | null;
  _count?: { entries?: number | null } | null;
  entries?: Array<{
    id: string;
    date: Date;
    name: string;
    isRecurringAnnual?: boolean | null;
    source?: string | null;
  }> | null;
}) {
  const entries = calendar.entries?.map(formatHolidayCalendarEntry) ?? [];

  return {
    id: calendar.id,
    name: calendar.name,
    scopeType: calendar.scopeType,
    stateCode: calendar.stateCode ?? null,
    isActive: calendar.isActive ?? true,
    priority: calendar.priority ?? 0,
    country: calendar.country
      ? {
          id: calendar.country.id,
          code: calendar.country.code,
          name: calendar.country.name,
        }
      : null,
    metroCity: calendar.metroCity
      ? {
          id: calendar.metroCity.id,
          name: calendar.metroCity.name,
        }
      : null,
    entryCount: calendar._count?.entries ?? entries.length,
    entries,
  };
}

function formatCountry(country: {
  id: string;
  code: string;
  name: string;
  dailyWorkingHours: number;
  scheduleRules?: Prisma.JsonValue | null;
  isActive?: boolean | null;
  metroCities?: Array<{ id: string; name: string }> | null;
  _count?: { resources?: number | null } | null;
}) {
  return {
    id: country.id,
    code: country.code,
    name: country.name,
    dailyWorkingHours: country.dailyWorkingHours,
    scheduleRules: country.scheduleRules ?? null,
    isActive: country.isActive ?? true,
    resourceCount: country._count?.resources ?? null,
    metroCities: (country.metroCities ?? []).map((city) => ({
      id: city.id,
      name: city.name,
    })),
    cities: (country.metroCities ?? []).map((city) => city.name),
  };
}

function createUtcDate(year: number, monthIndex: number, day: number): Date {
  return new Date(Date.UTC(year, monthIndex, day));
}

function resolveHolidayPeriod(input: {
  year?: number;
  periodStart?: string;
  periodEnd?: string;
}): { year: number | null; periodStart: Date; periodEnd: Date } {
  if (input.periodStart || input.periodEnd) {
    if (!input.periodStart || !input.periodEnd) {
      throw new Error("periodStart and periodEnd must both be provided when using a custom holiday range.");
    }

    const periodStart = new Date(`${input.periodStart}T00:00:00.000Z`);
    const periodEnd = new Date(`${input.periodEnd}T00:00:00.000Z`);
    if (Number.isNaN(periodStart.getTime())) {
      throw new Error(`Invalid periodStart: ${input.periodStart}`);
    }
    if (Number.isNaN(periodEnd.getTime())) {
      throw new Error(`Invalid periodEnd: ${input.periodEnd}`);
    }
    if (periodEnd < periodStart) {
      throw new Error("periodEnd must be on or after periodStart.");
    }

    return { year: null, periodStart, periodEnd };
  }

  const year = input.year ?? new Date().getUTCFullYear();
  return {
    year,
    periodStart: createUtcDate(year, 0, 1),
    periodEnd: createUtcDate(year, 11, 31),
  };
}

const ASSISTANT_VACATION_REQUEST_TYPES = [
  VacationType.ANNUAL,
  VacationType.SICK,
  VacationType.OTHER,
] as const;

function parseAssistantVacationRequestType(input: string): VacationType {
  const normalized = input.trim().toUpperCase();
  if (normalized === VacationType.PUBLIC_HOLIDAY) {
    throw new Error("PUBLIC_HOLIDAY requests cannot be created manually. Manage public holidays through holiday calendars instead.");
  }

  if ((ASSISTANT_VACATION_REQUEST_TYPES as readonly string[]).includes(normalized)) {
    return normalized as VacationType;
  }

  throw new Error(`Invalid vacation type: ${input}. Valid types: ${ASSISTANT_VACATION_REQUEST_TYPES.join(", ")}.`);
}

function parseIsoDate(value: string, fieldName: string): Date {
  const parsed = new Date(`${value}T00:00:00.000Z`);
  if (Number.isNaN(parsed.getTime())) {
    throw new Error(`Invalid ${fieldName}: ${value}`);
  }
  return parsed;
}

function toDate(value: Date | string): Date {
  return value instanceof Date ? value : new Date(value);
}

type AssistantToolErrorResult = { error: string };

function toAssistantNotFoundError(
  error: unknown,
  message: string,
): AssistantToolErrorResult | null {
  if (error instanceof TRPCError && error.code === "NOT_FOUND") {
    return { error: message };
  }
  return null;
}

async function resolveEntityOrAssistantError<T>(
  resolve: () => Promise<T>,
  notFoundMessage: string,
): Promise<T | AssistantToolErrorResult> {
  try {
    return await resolve();
  } catch (error) {
    const mapped = toAssistantNotFoundError(error, notFoundMessage);
    if (mapped) {
      return mapped;
    }
    throw error;
  }
}

async function resolveProjectIdentifier(
  ctx: ToolContext,
  identifier: string,
) {
  const caller = createProjectCaller(createScopedCallerContext(ctx));
  return resolveEntityOrAssistantError(
    () => caller.resolveByIdentifier({ identifier }),
    `Project not found: ${identifier}`,
  );
}

async function resolveResourceIdentifier(
  ctx: ToolContext,
  identifier: string,
) {
  const caller = createResourceCaller(createScopedCallerContext(ctx));
  return resolveEntityOrAssistantError(
    () => caller.resolveByIdentifier({ identifier }),
    `Resource not found: ${identifier}`,
  );
}

function createScopedCallerContext(ctx: ToolContext): TRPCContext {
  if (!ctx.session?.user || !ctx.dbUser) {
    throw new Error("Authenticated assistant context is required for this tool.");
  }

  return {
    session: ctx.session,
    db: ctx.db,
    dbUser: ctx.dbUser,
    roleDefaults: ctx.roleDefaults ?? null,
  };
}

function sanitizeWebhook<T extends { secret?: string | null }>(webhook: T) {
  const { secret: _secret, ...rest } = webhook;
  return {
    ...rest,
    hasSecret: Boolean(webhook.secret),
  };
}

function sanitizeWebhookList<T extends { secret?: string | null }>(webhooks: T[]) {
  return webhooks.map((webhook) => sanitizeWebhook(webhook));
}

// ─── Tool Definitions ───────────────────────────────────────────────────────

export const TOOL_DEFINITIONS: ToolDef[] = [
  // ── READ TOOLS ──
  {
    type: "function",
    function: {
      name: "search_resources",
      description: "Search for resources (employees) by name, employee ID, chapter, country, metro city, org unit, or role. Returns a list of matching resources with key details.",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search term (matches displayName, eid, chapter)" },
          country: { type: "string", description: "Filter by country name or code (e.g. 'Spain', 'ES', 'Deutschland', 'DE')" },
          metroCity: { type: "string", description: "Filter by metro city name (e.g. 'Madrid', 'München')" },
          orgUnit: { type: "string", description: "Filter by org unit name (partial match)" },
          roleName: { type: "string", description: "Filter by role name (partial match)" },
          isActive: { type: "boolean", description: "Filter by active status. Default: true" },
          limit: { type: "integer", description: "Max results. Default: 50" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_resource",
      description: "Get detailed information about a single resource by ID, employee ID (eid), or name.",
      parameters: {
        type: "object",
        properties: {
          identifier: { type: "string", description: "Resource ID, employee ID (eid like EMP-001), or display name" },
        },
        required: ["identifier"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "search_projects",
      description: "Search for projects by name, short code, status, or client.",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search term (matches name, shortCode)" },
          status: { type: "string", description: "Filter by status: DRAFT, ACTIVE, ON_HOLD, COMPLETED, CANCELLED" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_project",
      description: "Get detailed information about a single project by ID or short code, including top allocations.",
      parameters: {
        type: "object",
        properties: {
          identifier: { type: "string", description: "Project ID or short code (e.g. Z033T593)" },
        },
        required: ["identifier"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "find_best_project_resource",
      description: "Advanced assistant tool: find the best already-assigned resource on a project for a given period, ranked by remaining capacity or LCR. Holiday- and vacation-aware. Requires viewCosts and advanced assistant permissions.",
      parameters: {
        type: "object",
        properties: {
          projectIdentifier: { type: "string", description: "Project ID, short code, or project name." },
          startDate: { type: "string", description: "Optional start date in YYYY-MM-DD. Default: today." },
          endDate: { type: "string", description: "Optional end date in YYYY-MM-DD. If omitted, durationDays is used." },
          durationDays: { type: "integer", description: "Optional duration in calendar days when endDate is omitted. Default: 21." },
          minHoursPerDay: { type: "number", description: "Minimum remaining availability per effective working day. Default: 3." },
          rankingMode: { type: "string", description: "Ranking mode: lowest_lcr, highest_remaining_hours_per_day, or highest_remaining_hours. Default: lowest_lcr." },
          chapter: { type: "string", description: "Optional chapter filter for candidate resources." },
          roleName: { type: "string", description: "Optional role filter for candidate resources." },
        },
        required: ["projectIdentifier"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_timeline_entries_view",
      description: "Advanced assistant tool: read-only timeline entries view with the same timeline/disposition readmodel used by the app. Returns allocations, demands, assignments, and matching holiday overlays for a period.",
      parameters: {
        type: "object",
        properties: {
          startDate: { type: "string", description: "Optional start date in YYYY-MM-DD. Default: today." },
          endDate: { type: "string", description: "Optional end date in YYYY-MM-DD. If omitted, durationDays is used." },
          durationDays: { type: "integer", description: "Optional duration in calendar days when endDate is omitted. Default: 21." },
          resourceIds: { type: "array", items: { type: "string" }, description: "Optional resource IDs to scope the view." },
          projectIds: { type: "array", items: { type: "string" }, description: "Optional project IDs to scope the view." },
          clientIds: { type: "array", items: { type: "string" }, description: "Optional client IDs to scope the view." },
          chapters: { type: "array", items: { type: "string" }, description: "Optional chapter filters." },
          eids: { type: "array", items: { type: "string" }, description: "Optional employee IDs to scope the view." },
          countryCodes: { type: "array", items: { type: "string" }, description: "Optional country codes such as DE or ES." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_timeline_holiday_overlays",
      description: "Advanced assistant tool: read-only holiday overlays for the timeline, resolved with the same holiday logic as the app. Useful to explain regional holiday differences for assigned or filtered resources.",
      parameters: {
        type: "object",
        properties: {
          startDate: { type: "string", description: "Optional start date in YYYY-MM-DD. Default: today." },
          endDate: { type: "string", description: "Optional end date in YYYY-MM-DD. If omitted, durationDays is used." },
          durationDays: { type: "integer", description: "Optional duration in calendar days when endDate is omitted. Default: 21." },
          resourceIds: { type: "array", items: { type: "string" }, description: "Optional resource IDs to scope the overlays." },
          projectIds: { type: "array", items: { type: "string" }, description: "Optional project IDs to scope the overlays via matching assignments." },
          clientIds: { type: "array", items: { type: "string" }, description: "Optional client IDs to scope the overlays via matching projects." },
          chapters: { type: "array", items: { type: "string" }, description: "Optional chapter filters." },
          eids: { type: "array", items: { type: "string" }, description: "Optional employee IDs to scope the overlays." },
          countryCodes: { type: "array", items: { type: "string" }, description: "Optional country codes such as DE or ES." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_project_timeline_context",
      description: "Advanced assistant tool: read-only project timeline/disposition context. Reuses the same project context readmodel as the app and adds holiday overlays plus cross-project overlap summaries for assigned resources.",
      parameters: {
        type: "object",
        properties: {
          projectIdentifier: { type: "string", description: "Project ID, short code, or project name." },
          startDate: { type: "string", description: "Optional holiday/conflict window start date in YYYY-MM-DD. Defaults to the project start date when available." },
          endDate: { type: "string", description: "Optional holiday/conflict window end date in YYYY-MM-DD. Defaults to the project end date when available." },
          durationDays: { type: "integer", description: "Optional duration in calendar days when endDate is omitted." },
        },
        required: ["projectIdentifier"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "preview_project_shift",
      description: "Advanced assistant tool: read-only preview of the timeline shift validation for a project. Uses the same preview logic as the timeline router and does not write changes.",
      parameters: {
        type: "object",
        properties: {
          projectIdentifier: { type: "string", description: "Project ID, short code, or project name." },
          newStartDate: { type: "string", description: "New start date in YYYY-MM-DD." },
          newEndDate: { type: "string", description: "New end date in YYYY-MM-DD." },
        },
        required: ["projectIdentifier", "newStartDate", "newEndDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_timeline_allocation_inline",
      description: "Advanced assistant mutation: update a timeline allocation inline with the same manager/admin + manageAllocations validation as the timeline API. Supports hours/day, dates, includeSaturday, and role changes. Requires useAssistantAdvancedTools.",
      parameters: {
        type: "object",
        properties: {
          allocationId: { type: "string", description: "Allocation, assignment, or demand row ID to update." },
          hoursPerDay: { type: "number", description: "Optional new booked hours per day." },
          startDate: { type: "string", description: "Optional new start date in YYYY-MM-DD." },
          endDate: { type: "string", description: "Optional new end date in YYYY-MM-DD." },
          includeSaturday: { type: "boolean", description: "Optional Saturday-working flag stored in metadata." },
          role: { type: "string", description: "Optional new role label." },
        },
        required: ["allocationId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "apply_timeline_project_shift",
      description: "Advanced assistant mutation: apply the real timeline project shift mutation, including validation, date movement, cost recalculation, audit logging, and SSE. Requires manager/admin, manageAllocations, and useAssistantAdvancedTools.",
      parameters: {
        type: "object",
        properties: {
          projectIdentifier: { type: "string", description: "Project ID, short code, or project name." },
          newStartDate: { type: "string", description: "New project start date in YYYY-MM-DD." },
          newEndDate: { type: "string", description: "New project end date in YYYY-MM-DD." },
        },
        required: ["projectIdentifier", "newStartDate", "newEndDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "quick_assign_timeline_resource",
      description: "Advanced assistant mutation: create a timeline quick-assignment with the same manager/admin + manageAllocations rules as the timeline UI. Resolves resource and project identifiers before calling the real mutation. Requires useAssistantAdvancedTools.",
      parameters: {
        type: "object",
        properties: {
          resourceIdentifier: { type: "string", description: "Resource ID, eid, or display name." },
          projectIdentifier: { type: "string", description: "Project ID, short code, or project name." },
          startDate: { type: "string", description: "Start date in YYYY-MM-DD." },
          endDate: { type: "string", description: "End date in YYYY-MM-DD." },
          hoursPerDay: { type: "number", description: "Hours per day. Default: 8." },
          role: { type: "string", description: "Role label. Default: Team Member." },
          roleId: { type: "string", description: "Optional concrete role ID." },
          status: { type: "string", enum: ["PROPOSED", "CONFIRMED", "ACTIVE", "COMPLETED", "CANCELLED"], description: "Assignment status. Default: PROPOSED." },
        },
        required: ["resourceIdentifier", "projectIdentifier", "startDate", "endDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "batch_quick_assign_timeline_resources",
      description: "Advanced assistant mutation: batch-create timeline quick-assignments using the same timeline router logic, permission checks, and audit/SSE side effects as the app. Requires manager/admin, manageAllocations, and useAssistantAdvancedTools.",
      parameters: {
        type: "object",
        properties: {
          assignments: {
            type: "array",
            minItems: 1,
            maxItems: 50,
            items: {
              type: "object",
              properties: {
                resourceIdentifier: { type: "string", description: "Resource ID, eid, or display name." },
                projectIdentifier: { type: "string", description: "Project ID, short code, or project name." },
                startDate: { type: "string", description: "Start date in YYYY-MM-DD." },
                endDate: { type: "string", description: "End date in YYYY-MM-DD." },
                hoursPerDay: { type: "number", description: "Hours per day. Default: 8." },
                role: { type: "string", description: "Role label. Default: Team Member." },
                status: { type: "string", enum: ["PROPOSED", "CONFIRMED", "ACTIVE", "COMPLETED", "CANCELLED"], description: "Assignment status. Default: PROPOSED." },
              },
              required: ["resourceIdentifier", "projectIdentifier", "startDate", "endDate"],
            },
            description: "Assignment rows to create in one batch.",
          },
        },
        required: ["assignments"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "batch_shift_timeline_allocations",
      description: "Advanced assistant mutation: shift multiple timeline allocations by a shared day delta using the real timeline batch move/resize mutation. Requires manager/admin, manageAllocations, and useAssistantAdvancedTools.",
      parameters: {
        type: "object",
        properties: {
          allocationIds: { type: "array", items: { type: "string" }, description: "Allocation IDs to shift." },
          daysDelta: { type: "integer", description: "Signed day delta to apply." },
          mode: { type: "string", enum: ["move", "resize-start", "resize-end"], description: "Shift mode. Default: move." },
        },
        required: ["allocationIds", "daysDelta"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_allocations",
      description: "List assignments/allocations, optionally filtered by resource or project. Shows who is assigned where, hours/day, dates, and cost.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Filter by resource ID" },
          projectId: { type: "string", description: "Filter by project ID" },
          resourceName: { type: "string", description: "Filter by resource name (partial match)" },
          projectCode: { type: "string", description: "Filter by project short code (partial match)" },
          status: { type: "string", description: "Filter by status: PROPOSED, CONFIRMED, ACTIVE, COMPLETED, CANCELLED" },
          limit: { type: "integer", description: "Max results. Default: 30" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_budget_status",
      description: "Get the budget status of a project: total budget, confirmed/proposed costs, remaining, utilization percentage.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID or short code" },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_vacation_balance",
      description: "Get the holiday-aware vacation balance for a resource via the real entitlement workflow. Authenticated users can read their own balance; manager/admin/controller can read broader balances.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, EID, or display name" },
          year: { type: "integer", description: "Year. Default: current year" },
        },
        required: ["resourceId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_vacations_upcoming",
      description: "List upcoming vacations across all resources, or for a specific resource/team. Shows who is off when.",
      parameters: {
        type: "object",
        properties: {
          resourceName: { type: "string", description: "Filter by resource name (partial match)" },
          chapter: { type: "string", description: "Filter by chapter/team" },
          daysAhead: { type: "integer", description: "How many days ahead to look. Default: 30" },
          limit: { type: "integer", description: "Max results. Default: 30" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_holidays_by_region",
      description: "List resolved public holidays for a country, federal state, and optionally a city in a given year or date range. Use this to compare regions such as Bayern vs Hamburg.",
      parameters: {
        type: "object",
        properties: {
          countryCode: { type: "string", description: "Country code such as DE, ES, US, IN." },
          federalState: { type: "string", description: "Federal state / region code, e.g. BY, HH, NRW." },
          metroCity: { type: "string", description: "Optional city name for local city-specific holidays, e.g. Augsburg." },
          year: { type: "integer", description: "Full year, e.g. 2026. Default: current year." },
          periodStart: { type: "string", description: "Optional start date in YYYY-MM-DD. Requires periodEnd." },
          periodEnd: { type: "string", description: "Optional end date in YYYY-MM-DD. Requires periodStart." },
        },
        required: ["countryCode"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_resource_holidays",
      description: "List resolved public holidays for a specific resource based on that person's country, federal state, and city context.",
      parameters: {
        type: "object",
        properties: {
          identifier: { type: "string", description: "Resource ID, EID, or display name." },
          year: { type: "integer", description: "Full year, e.g. 2026. Default: current year." },
          periodStart: { type: "string", description: "Optional start date in YYYY-MM-DD. Requires periodEnd." },
          periodEnd: { type: "string", description: "Optional end date in YYYY-MM-DD. Requires periodStart." },
        },
        required: ["identifier"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_holiday_calendars",
      description: "List holiday calendars including scope, assignment, active flag, priority, and entry count. Useful to inspect the calendar-editor configuration context.",
      parameters: {
        type: "object",
        properties: {
          includeInactive: { type: "boolean", description: "Include inactive calendars. Default: false." },
          countryCode: { type: "string", description: "Optional country code filter such as DE or ES." },
          scopeType: { type: "string", description: "Optional scope filter: COUNTRY, STATE, CITY." },
          stateCode: { type: "string", description: "Optional state/region code filter such as BY or NRW." },
          metroCity: { type: "string", description: "Optional city-name filter." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_holiday_calendar",
      description: "Get a single holiday calendar including all entries. Accepts either the calendar ID or its name.",
      parameters: {
        type: "object",
        properties: {
          identifier: { type: "string", description: "Holiday calendar ID or name." },
        },
        required: ["identifier"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "preview_resolved_holiday_calendar",
      description: "Preview the resolved holiday result for a country/state/city scope and year, including which calendar each holiday comes from.",
      parameters: {
        type: "object",
        properties: {
          countryId: { type: "string", description: "Country ID." },
          stateCode: { type: "string", description: "Optional state/region code." },
          metroCityId: { type: "string", description: "Optional metro city ID for city-specific preview." },
          year: { type: "integer", description: "Full year, e.g. 2026." },
        },
        required: ["countryId", "year"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_holiday_calendar",
      description: "Create a holiday calendar for a country, state, or city scope. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          name: { type: "string", description: "Calendar name." },
          scopeType: { type: "string", description: "COUNTRY, STATE, or CITY." },
          countryId: { type: "string", description: "Country ID." },
          stateCode: { type: "string", description: "Required for STATE calendars." },
          metroCityId: { type: "string", description: "Required for CITY calendars." },
          isActive: { type: "boolean", description: "Whether the calendar is active. Default: true." },
          priority: { type: "integer", description: "Priority used during calendar resolution. Default: 0." },
        },
        required: ["name", "scopeType", "countryId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_holiday_calendar",
      description: "Update an existing holiday calendar. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Holiday calendar ID." },
          data: {
            type: "object",
            properties: {
              name: { type: "string" },
              stateCode: { type: "string" },
              metroCityId: { type: "string" },
              isActive: { type: "boolean" },
              priority: { type: "integer" },
            },
          },
        },
        required: ["id", "data"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "delete_holiday_calendar",
      description: "Delete a holiday calendar and all of its entries. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Holiday calendar ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_holiday_calendar_entry",
      description: "Create a holiday entry in an existing calendar. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          holidayCalendarId: { type: "string", description: "Holiday calendar ID." },
          date: { type: "string", description: "Date in YYYY-MM-DD format." },
          name: { type: "string", description: "Holiday name." },
          isRecurringAnnual: { type: "boolean", description: "Whether the holiday repeats every year." },
          source: { type: "string", description: "Optional source or legal basis." },
        },
        required: ["holidayCalendarId", "date", "name"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_holiday_calendar_entry",
      description: "Update an existing holiday calendar entry. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Holiday calendar entry ID." },
          data: {
            type: "object",
            properties: {
              date: { type: "string", description: "Date in YYYY-MM-DD format." },
              name: { type: "string" },
              isRecurringAnnual: { type: "boolean" },
              source: { type: "string" },
            },
          },
        },
        required: ["id", "data"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "delete_holiday_calendar_entry",
      description: "Delete a holiday calendar entry. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Holiday calendar entry ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_roles",
      description: "List all available roles with their colors.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "search_by_skill",
      description: "Find resources that have a specific skill.",
      parameters: {
        type: "object",
        properties: {
          skill: { type: "string", description: "Skill name to search for" },
        },
        required: ["skill"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_statistics",
      description: "Get overview statistics: total resources, projects, active allocations, budget summary, projects by status, chapter breakdown.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_chargeability",
      description: "Get chargeability data for a resource in a given month: hours booked vs available, chargeability %, target comparison.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, eid, or name" },
          month: { type: "string", description: "Month in YYYY-MM format, e.g. 2026-03. Default: current month" },
        },
        required: ["resourceId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_chargeability_report",
      description: "Get the detailed chargeability report readmodel for a month range, including group totals and per-resource month series. Requires controller/manager/admin access, viewCosts, and useAssistantAdvancedTools.",
      parameters: {
        type: "object",
        properties: {
          startMonth: { type: "string", description: "Start month in YYYY-MM format." },
          endMonth: { type: "string", description: "End month in YYYY-MM format." },
          orgUnitId: { type: "string", description: "Optional org unit filter." },
          managementLevelGroupId: { type: "string", description: "Optional management level group filter." },
          countryId: { type: "string", description: "Optional country filter." },
          includeProposed: { type: "boolean", description: "Whether proposed bookings should count towards chargeability. Default: false." },
          resourceQuery: { type: "string", description: "Optional resource filter by name or eid after loading the report." },
          resourceLimit: { type: "integer", description: "Maximum number of resources returned. Default: 25, max 100." },
        },
        required: ["startMonth", "endMonth"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_resource_computation_graph",
      description: "Get the resource computation graph with transparent SAH, holiday, absence, allocation, chargeability, and budget derivation factors. Requires controller/manager/admin access, viewCosts, and useAssistantAdvancedTools.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, eid, or display name." },
          month: { type: "string", description: "Month in YYYY-MM format." },
          domain: { type: "string", enum: ["INPUT", "SAH", "ALLOCATION", "RULES", "CHARGEABILITY", "BUDGET"], description: "Optional domain filter for graph nodes." },
          includeLinks: { type: "boolean", description: "Include graph links for the selected nodes. Default: false." },
        },
        required: ["resourceId", "month"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_project_computation_graph",
      description: "Get the project computation graph with estimate, commercial, effort, experience, spread, and budget derivation factors. Requires controller/manager/admin access, viewCosts, and useAssistantAdvancedTools.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID, short code, or project name." },
          domain: { type: "string", enum: ["INPUT", "ESTIMATE", "COMMERCIAL", "EXPERIENCE", "EFFORT", "SPREAD", "BUDGET"], description: "Optional domain filter for graph nodes." },
          includeLinks: { type: "boolean", description: "Include graph links for the selected nodes. Default: false." },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "search_estimates",
      description: "Search for estimates (cost/effort estimates) by project or name. Returns estimate name, status, version count.",
      parameters: {
        type: "object",
        properties: {
          projectCode: { type: "string", description: "Project short code to filter by" },
          query: { type: "string", description: "Search term (matches estimate name)" },
          status: { type: "string", description: "Filter by status: DRAFT, IN_REVIEW, APPROVED, ARCHIVED" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_clients",
      description: "List clients/customers. Can search by name or code.",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search term (matches name or code)" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_org_units",
      description: "List organizational units (departments, teams) with their hierarchy.",
      parameters: {
        type: "object",
        properties: {
          level: { type: "integer", description: "Filter by org level (5, 6, or 7)" },
        },
      },
    },
  },

  // ── NAVIGATION TOOLS ──
  {
    type: "function",
    function: {
      name: "navigate_to_page",
      description: "Navigate the user to a specific page in CapaKraken, optionally with filters. Use this when the user wants to see data on a specific page (e.g. 'show me on the timeline', 'open the resources page').",
      parameters: {
        type: "object",
        properties: {
          page: {
            type: "string",
            description: "Page name: timeline, dashboard, resources, projects, allocations, staffing, estimates, vacations, my-vacations, roles, skills-analytics, chargeability, computation-graph",
          },
          eids: { type: "string", description: "Comma-separated employee IDs to filter (for timeline)" },
          chapters: { type: "string", description: "Comma-separated chapters to filter (for timeline)" },
          projectIds: { type: "string", description: "Comma-separated project IDs to filter (for timeline)" },
          clientIds: { type: "string", description: "Comma-separated client IDs to filter (for timeline)" },
          countryCodes: { type: "string", description: "Comma-separated country codes to filter (e.g. 'ES,DE' for Spain and Germany, for timeline)" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD (for timeline)" },
          days: { type: "integer", description: "Number of days to show (for timeline)" },
        },
        required: ["page"],
      },
    },
  },

  // ── WRITE TOOLS ──
  {
    type: "function",
    function: {
      name: "create_allocation",
      description: "Create a new allocation/booking for a resource on a project. Requires manageAllocations permission. Always confirm with the user before calling this. Created with PROPOSED status.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID" },
          projectId: { type: "string", description: "Project ID" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
          hoursPerDay: { type: "number", description: "Hours per day (e.g. 8)" },
          role: { type: "string", description: "Optional role name" },
        },
        required: ["resourceId", "projectId", "startDate", "endDate", "hoursPerDay"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "cancel_allocation",
      description: "Cancel an existing allocation. Can find by allocation ID, or by resource name + project code + date range. Requires manageAllocations permission. Always confirm with the user before calling this.",
      parameters: {
        type: "object",
        properties: {
          allocationId: { type: "string", description: "Allocation ID (if known)" },
          resourceName: { type: "string", description: "Resource name (partial match)" },
          projectCode: { type: "string", description: "Project short code (partial match)" },
          startDate: { type: "string", description: "Filter by start date YYYY-MM-DD" },
          endDate: { type: "string", description: "Filter by end date YYYY-MM-DD" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_resource",
      description: "Update a resource's details. Requires manageResources permission. Always confirm with the user before calling this.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Resource ID, EID, or display name" },
          displayName: { type: "string", description: "New display name" },
          fte: { type: "number", description: "New FTE (0.0-1.0)" },
          lcrCents: { type: "integer", description: "New LCR in cents (e.g. 8500 = 85.00 EUR/h)" },
          chapter: { type: "string", description: "New chapter" },
          chargeabilityTarget: { type: "number", description: "New chargeability target (0-100)" },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_allocation_status",
      description: "Change the status of an existing allocation. Can reactivate cancelled allocations, confirm proposed ones, etc. Requires manageAllocations permission. Always confirm with the user before calling.",
      parameters: {
        type: "object",
        properties: {
          allocationId: { type: "string", description: "Allocation ID" },
          resourceName: { type: "string", description: "Resource name (partial match, used if no allocationId)" },
          projectCode: { type: "string", description: "Project short code (partial match, used if no allocationId)" },
          startDate: { type: "string", description: "Start date filter YYYY-MM-DD (used if no allocationId)" },
          newStatus: { type: "string", description: "New status: PROPOSED, CONFIRMED, ACTIVE, COMPLETED, CANCELLED" },
        },
        required: ["newStatus"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_project",
      description: "Update a project's details. Requires manageProjects permission. Always confirm with the user before calling this.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Project ID, short code, or project name" },
          name: { type: "string", description: "New project name" },
          budgetCents: { type: "integer", description: "New budget in cents (e.g. 10000000 = 100,000 EUR)" },
          winProbability: { type: "integer", description: "Win probability 0-100" },
          status: { type: "string", description: "New status: DRAFT, ACTIVE, ON_HOLD, COMPLETED, CANCELLED" },
          responsiblePerson: { type: "string", description: "Name of the responsible person. Must match an existing resource's display name (case-insensitive search)." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_project",
      description: "Create a new project. Requires manageProjects permission. Always confirm with the user before calling this. The project is created in DRAFT status by default.",
      parameters: {
        type: "object",
        properties: {
          shortCode: { type: "string", description: "Unique project code, uppercase alphanumeric with hyphens/underscores (e.g. 'PROJ-001')" },
          name: { type: "string", description: "Project name" },
          orderType: { type: "string", description: "Order type: BD, CHARGEABLE, INTERNAL, OVERHEAD" },
          allocationType: { type: "string", description: "Allocation type: INT or EXT. Default: INT" },
          budgetCents: { type: "integer", description: "Budget in cents (e.g. 10000000 = 100,000 EUR)" },
          startDate: { type: "string", description: "Start date (YYYY-MM-DD)" },
          endDate: { type: "string", description: "End date (YYYY-MM-DD)" },
          winProbability: { type: "integer", description: "Win probability 0-100. Default: 100" },
          status: { type: "string", description: "Initial status: DRAFT, ACTIVE, ON_HOLD. Default: DRAFT" },
          responsiblePerson: { type: "string", description: "Name of the responsible person. Must match an existing resource's display name (case-insensitive search)." },
          color: { type: "string", description: "Hex color (e.g. '#3b82f6')" },
          blueprintName: { type: "string", description: "Blueprint name to look up and attach (partial match)" },
          clientName: { type: "string", description: "Client name to look up and attach (partial match)" },
        },
        required: ["shortCode", "name", "orderType", "budgetCents", "startDate", "endDate", "responsiblePerson"],
      },
    },
  },

  // ── RESOURCE MANAGEMENT ──
  {
    type: "function",
    function: {
      name: "create_resource",
      description: "Create a new resource (employee). Requires manageResources permission. Always confirm with the user before calling.",
      parameters: {
        type: "object",
        properties: {
          eid: { type: "string", description: "Employee ID (e.g. EMP-042)" },
          displayName: { type: "string", description: "Full name" },
          email: { type: "string", description: "Email address" },
          fte: { type: "number", description: "FTE 0.0-1.0. Default: 1" },
          lcrCents: { type: "integer", description: "Loaded cost rate in cents (e.g. 8500 = 85 EUR/h)" },
          ucrCents: { type: "integer", description: "Unloaded cost rate in cents" },
          chapter: { type: "string", description: "Chapter/team name" },
          chargeabilityTarget: { type: "number", description: "Chargeability target 0-100. Default: 80" },
          roleName: { type: "string", description: "Role name (partial match)" },
          countryCode: { type: "string", description: "Country code (e.g. DE, ES)" },
          orgUnitName: { type: "string", description: "Org unit name (partial match)" },
          postalCode: { type: "string", description: "Postal code" },
        },
        required: ["eid", "displayName", "email", "lcrCents"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "deactivate_resource",
      description: "Deactivate a resource (soft delete). Requires manageResources permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          identifier: { type: "string", description: "Resource ID, eid, or name" },
        },
        required: ["identifier"],
      },
    },
  },

  // ── VACATION MANAGEMENT ──
  {
    type: "function",
    function: {
      name: "create_vacation",
      description: "Create a vacation/leave request through the real vacation workflow. Any authenticated user can request leave for their own resource; manager/admin can create requests for others. Always confirm with the user.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, EID, or display name" },
          type: {
            type: "string",
            enum: ["ANNUAL", "SICK", "OTHER"],
            description: "Vacation type. PUBLIC_HOLIDAY requests are managed through holiday calendars, not manual vacation requests.",
          },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
          isHalfDay: { type: "boolean", description: "Half day? Default: false" },
          halfDayPart: { type: "string", description: "MORNING or AFTERNOON (if half day)" },
          note: { type: "string", description: "Optional note" },
        },
        required: ["resourceId", "type", "startDate", "endDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "approve_vacation",
      description: "Approve a vacation request through the real vacation workflow. Manager or admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          vacationId: { type: "string", description: "Vacation ID" },
        },
        required: ["vacationId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "reject_vacation",
      description: "Reject a pending vacation request through the real vacation workflow. Manager or admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          vacationId: { type: "string", description: "Vacation ID" },
          reason: { type: "string", description: "Rejection reason" },
        },
        required: ["vacationId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "cancel_vacation",
      description: "Cancel a vacation request through the real vacation workflow. Users can cancel their own requests; manager/admin can cancel any request. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          vacationId: { type: "string", description: "Vacation ID" },
        },
        required: ["vacationId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_pending_vacation_approvals",
      description: "List vacation requests awaiting approval.",
      parameters: {
        type: "object",
        properties: {
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_team_vacation_overlap",
      description: "Check if team members have overlapping vacations in a date range.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID to check overlap for" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
        },
        required: ["resourceId", "startDate", "endDate"],
      },
    },
  },

  // ── ENTITLEMENT ──
  {
    type: "function",
    function: {
      name: "get_entitlement_summary",
      description: "Get vacation entitlement year summary for all resources or a specific resource.",
      parameters: {
        type: "object",
        properties: {
          year: { type: "integer", description: "Year. Default: current year" },
          resourceName: { type: "string", description: "Filter by resource name (optional)" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "set_entitlement",
      description: "Set the annual vacation entitlement for a resource/year through the real entitlement workflow. Manager or admin role required. Carryover is computed automatically. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, EID, or display name" },
          year: { type: "integer", description: "Year" },
          entitledDays: { type: "number", description: "Number of entitled vacation days" },
        },
        required: ["resourceId", "year", "entitledDays"],
      },
    },
  },

  // ── DEMAND / STAFFING ──
  {
    type: "function",
    function: {
      name: "list_demands",
      description: "List staffing demand requirements for projects. Shows open positions that need to be filled.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Filter by project ID or short code" },
          status: { type: "string", description: "Filter by status: OPEN, PARTIALLY_FILLED, FILLED, CANCELLED" },
          limit: { type: "integer", description: "Max results. Default: 30" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_demand",
      description: "Create a staffing demand requirement on a project. Requires manageAllocations permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID or short code" },
          roleName: { type: "string", description: "Role name for the demand" },
          headcount: { type: "integer", description: "Number of people needed. Default: 1" },
          hoursPerDay: { type: "number", description: "Hours per day required" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
        },
        required: ["projectId", "roleName", "hoursPerDay", "startDate", "endDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "fill_demand",
      description: "Fill/assign a resource to an open demand requirement. Requires manageAllocations permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          demandId: { type: "string", description: "Demand requirement ID" },
          resourceId: { type: "string", description: "Resource ID or name to assign" },
        },
        required: ["demandId", "resourceId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "check_resource_availability",
      description: "Check if a resource is available in a given date range (no conflicting allocations or vacations).",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, eid, or name" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
        },
        required: ["resourceId", "startDate", "endDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_staffing_suggestions",
      description: "Get AI-powered staffing suggestions for a project based on required skills, availability, and cost.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID or short code" },
          roleName: { type: "string", description: "Role to find candidates for" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
          limit: { type: "integer", description: "Max suggestions. Default: 5" },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "find_capacity",
      description: "Find resources with available capacity in a date range.",
      parameters: {
        type: "object",
        properties: {
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
          minHoursPerDay: { type: "number", description: "Minimum available hours/day. Default: 4" },
          roleName: { type: "string", description: "Filter by role name" },
          chapter: { type: "string", description: "Filter by chapter" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
        required: ["startDate", "endDate"],
      },
    },
  },

  // ── BLUEPRINT ──
  {
    type: "function",
    function: {
      name: "list_blueprints",
      description: "List available project blueprints with their field definitions.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_blueprint",
      description: "Get detailed blueprint with all field definitions and role presets.",
      parameters: {
        type: "object",
        properties: {
          identifier: { type: "string", description: "Blueprint ID or name (partial match)" },
        },
        required: ["identifier"],
      },
    },
  },

  // ── RATE CARDS ──
  {
    type: "function",
    function: {
      name: "list_rate_cards",
      description: "List rate cards with their effective dates and line items.",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search by name" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "resolve_rate",
      description: "Look up the applicable rate for a resource, role, or management level from rate cards.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID or name" },
          roleName: { type: "string", description: "Role name" },
          date: { type: "string", description: "Date to check rate for (YYYY-MM-DD). Default: today" },
        },
      },
    },
  },

  // ── ESTIMATES ──
  {
    type: "function",
    function: {
      name: "get_estimate_detail",
      description: "Get one estimate via the real estimate router, including versions, demand lines, metrics, assumptions, and linked project data. Controller/manager/admin access and viewCosts required.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID" },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_estimate_versions",
      description: "List estimate versions via the real estimate router, including status, timestamps, and artifact counts. Controller/manager/admin access required.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID" },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_estimate_version_snapshot",
      description: "Get an estimate version snapshot via the real estimate router, including totals, breakdowns, exports, and resource snapshots. Controller/manager/admin access and viewCosts required.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID" },
          versionId: { type: "string", description: "Optional explicit version ID. Defaults to the latest version." },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_estimate",
      description: "Create a new estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Optional project ID." },
          projectCode: { type: "string", description: "Optional project short code convenience alias." },
          name: { type: "string", description: "Estimate name." },
          opportunityId: { type: "string", description: "Optional opportunity/reference ID." },
          baseCurrency: { type: "string", description: "Base currency, e.g. EUR." },
          status: { type: "string", enum: ["DRAFT", "IN_REVIEW", "APPROVED", "ARCHIVED"] },
          versionLabel: { type: "string", description: "Optional working version label." },
          versionNotes: { type: "string", description: "Optional working version notes." },
          assumptions: { type: "array", items: { type: "object" }, description: "Estimate assumptions." },
          scopeItems: { type: "array", items: { type: "object" }, description: "Estimate scope items." },
          demandLines: { type: "array", items: { type: "object" }, description: "Estimate demand lines." },
          resourceSnapshots: { type: "array", items: { type: "object" }, description: "Resource cost snapshots." },
          metrics: { type: "array", items: { type: "object" }, description: "Optional metric overrides." },
        },
        required: ["name"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "clone_estimate",
      description: "Clone an existing estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          sourceEstimateId: { type: "string", description: "Source estimate ID." },
          name: { type: "string", description: "Optional cloned estimate name." },
          projectId: { type: "string", description: "Optional target project ID." },
          projectCode: { type: "string", description: "Optional target project short code convenience alias." },
        },
        required: ["sourceEstimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_estimate_draft",
      description: "Update the working draft of an estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Estimate ID." },
          projectId: { type: "string", description: "Optional linked project ID." },
          projectCode: { type: "string", description: "Optional linked project short code convenience alias." },
          name: { type: "string" },
          opportunityId: { type: "string" },
          baseCurrency: { type: "string" },
          status: { type: "string", enum: ["DRAFT", "IN_REVIEW", "APPROVED", "ARCHIVED"] },
          versionLabel: { type: "string" },
          versionNotes: { type: "string" },
          assumptions: { type: "array", items: { type: "object" } },
          scopeItems: { type: "array", items: { type: "object" } },
          demandLines: { type: "array", items: { type: "object" } },
          resourceSnapshots: { type: "array", items: { type: "object" } },
          metrics: { type: "array", items: { type: "object" } },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "submit_estimate_version",
      description: "Submit an estimate working version for review via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
          versionId: { type: "string", description: "Optional explicit version ID." },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "approve_estimate_version",
      description: "Approve a submitted estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
          versionId: { type: "string", description: "Optional explicit version ID." },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_estimate_revision",
      description: "Create a new working revision from the latest locked estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
          sourceVersionId: { type: "string", description: "Optional source version ID." },
          label: { type: "string", description: "Optional revision label." },
          notes: { type: "string", description: "Optional revision notes." },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_estimate_export",
      description: "Create an estimate export artifact via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
          versionId: { type: "string", description: "Optional explicit version ID." },
          format: { type: "string", enum: ["XLSX", "CSV", "JSON", "SAP", "MMP"], description: "Export format." },
        },
        required: ["estimateId", "format"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_estimate_planning_handoff",
      description: "Create planning allocations from an approved estimate version via the real estimate router. Manager/admin role and manageAllocations permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
          versionId: { type: "string", description: "Optional explicit approved version ID." },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "generate_estimate_weekly_phasing",
      description: "Generate weekly phasing for the working estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
          startDate: { type: "string", description: "Start date in YYYY-MM-DD." },
          endDate: { type: "string", description: "End date in YYYY-MM-DD." },
          pattern: { type: "string", enum: ["even", "front_loaded", "back_loaded", "custom"], description: "Distribution pattern." },
        },
        required: ["estimateId", "startDate", "endDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_estimate_weekly_phasing",
      description: "Get generated weekly phasing for an estimate via the real estimate router. Controller/manager/admin access required.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_estimate_commercial_terms",
      description: "Get estimate commercial terms via the real estimate router. Controller/manager/admin access required.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
          versionId: { type: "string", description: "Optional explicit version ID." },
        },
        required: ["estimateId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_estimate_commercial_terms",
      description: "Update estimate commercial terms on a working version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          estimateId: { type: "string", description: "Estimate ID." },
          versionId: { type: "string", description: "Optional explicit version ID." },
          terms: { type: "object", description: "Commercial terms payload." },
        },
        required: ["estimateId", "terms"],
      },
    },
  },

  // ── ROLES ──
  {
    type: "function",
    function: {
      name: "create_role",
      description: "Create a new role. Requires manager or admin role plus manageRoles permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          name: { type: "string", description: "Role name" },
          description: { type: "string", description: "Optional role description" },
          color: { type: "string", description: "Hex color (e.g. #3b82f6). Default: #6b7280" },
        },
        required: ["name"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_role",
      description: "Update a role. Requires manager or admin role plus manageRoles permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Role ID" },
          name: { type: "string", description: "New name" },
          description: { type: "string", description: "New description" },
          color: { type: "string", description: "New hex color" },
          isActive: { type: "boolean", description: "Set active state" },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "delete_role",
      description: "Delete a role. Requires manager or admin role plus manageRoles permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Role ID" },
        },
        required: ["id"],
      },
    },
  },

  // ── CLIENTS ──
  {
    type: "function",
    function: {
      name: "create_client",
      description: "Create a new client. Requires manager or admin role. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          name: { type: "string", description: "Client name" },
          code: { type: "string", description: "Client code" },
          parentId: { type: "string", description: "Optional parent client ID" },
          sortOrder: { type: "integer", description: "Sort order. Default: 0" },
          tags: { type: "array", items: { type: "string" }, description: "Optional client tags" },
        },
        required: ["name"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_client",
      description: "Update a client. Requires manager or admin role. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Client ID" },
          name: { type: "string", description: "New name" },
          code: { type: "string", description: "New code" },
          sortOrder: { type: "integer", description: "New sort order" },
          isActive: { type: "boolean", description: "Set active state" },
          parentId: { type: "string", description: "Parent client ID; use null to clear" },
          tags: { type: "array", items: { type: "string" }, description: "Replacement client tags" },
        },
        required: ["id"],
      },
    },
  },

  // ── ADMIN / CONFIG READ TOOLS ──
  {
    type: "function",
    function: {
      name: "list_countries",
      description: "List countries including working hours, schedule rules, active flag, and metro cities.",
      parameters: {
        type: "object",
        properties: {
          includeInactive: { type: "boolean", description: "Include inactive countries. Default: false." },
          search: { type: "string", description: "Optional country code or name search." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_country",
      description: "Get one country with schedule rules, active flag, metro cities, and resource count. Accepts ID, code, or name.",
      parameters: {
        type: "object",
        properties: {
          identifier: { type: "string", description: "Country ID, code, or name." },
        },
        required: ["identifier"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_country",
      description: "Create a country with daily working hours and optional schedule rules. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          code: { type: "string", description: "ISO country code such as DE or ES." },
          name: { type: "string", description: "Country name." },
          dailyWorkingHours: { type: "number", description: "Standard daily working hours." },
          scheduleRules: {
            type: "object",
            description: "Optional schedule rule object such as the Spain reduced-hours configuration.",
          },
        },
        required: ["code", "name"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_country",
      description: "Update a country including working hours, schedule rules, or active state. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Country ID." },
          data: {
            type: "object",
            properties: {
              code: { type: "string" },
              name: { type: "string" },
              dailyWorkingHours: { type: "number" },
              scheduleRules: { type: ["object", "null"] },
              isActive: { type: "boolean" },
            },
          },
        },
        required: ["id", "data"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_metro_city",
      description: "Create a metro city for a country. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          countryId: { type: "string", description: "Country ID." },
          name: { type: "string", description: "Metro city name." },
        },
        required: ["countryId", "name"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_metro_city",
      description: "Rename a metro city. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Metro city ID." },
          data: {
            type: "object",
            properties: {
              name: { type: "string" },
            },
          },
        },
        required: ["id", "data"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "delete_metro_city",
      description: "Delete a metro city when no resource is assigned to it. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Metro city ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_management_levels",
      description: "List management level groups and their levels with target percentages.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "list_utilization_categories",
      description: "List utilization categories (cost classification for projects).",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "list_calculation_rules",
      description: "List calculation rules for cost attribution and chargeability.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "list_effort_rules",
      description: "List effort estimation rules with their formulas and conditions.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "list_experience_multipliers",
      description: "List experience multipliers that adjust effort estimates based on seniority.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "list_users",
      description: "List all system users via the admin user router, including role and MFA state. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          limit: { type: "integer", description: "Max results. Default: 50" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_assignable_users",
      description: "List lightweight users available for assignment workflows. Manager or admin role required.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_current_user",
      description: "Get the authenticated user's own profile, role, and permission overrides.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_dashboard_layout",
      description: "Get the authenticated user's saved dashboard widget layout and last update timestamp.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "save_dashboard_layout",
      description: "Save the authenticated user's dashboard layout. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          layout: {
            type: "array",
            description: "Dashboard layout items as stored by the user router.",
            items: { type: "object" },
          },
        },
        required: ["layout"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_favorite_project_ids",
      description: "Get the authenticated user's favorite project IDs.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "toggle_favorite_project",
      description: "Add or remove a project from the authenticated user's favorites. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID." },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_column_preferences",
      description: "Get the authenticated user's saved table column preferences for all supported views.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "set_column_preferences",
      description: "Update the authenticated user's table column preferences for one view. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          view: {
            type: "string",
            enum: ["resources", "projects", "allocations", "vacations", "roles", "users", "blueprints"],
            description: "View key to update.",
          },
          visible: {
            type: "array",
            items: { type: "string" },
            description: "Visible column IDs.",
          },
          sort: {
            type: ["object", "null"],
            properties: {
              field: { type: "string" },
              dir: { type: "string", enum: ["asc", "desc"] },
            },
            description: "Sort state. Use null to clear it.",
          },
          rowOrder: {
            type: ["array", "null"],
            items: { type: "string" },
            description: "Optional row order. Use null to clear it.",
          },
        },
        required: ["view"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "generate_totp_secret",
      description: "Generate a new MFA TOTP secret and provisioning URI for the authenticated user. Always confirm first. The secret is sensitive.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "verify_and_enable_totp",
      description: "Verify a 6-digit MFA TOTP token and enable MFA for the authenticated user. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          token: { type: "string", description: "6-digit TOTP token." },
        },
        required: ["token"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_mfa_status",
      description: "Get the authenticated user's MFA status.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_active_user_count",
      description: "Get the number of users active in the last five minutes. Admin role required.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "create_user",
      description: "Create a new system user and auto-link a matching resource by email when possible. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          email: { type: "string", description: "User email address." },
          name: { type: "string", description: "Display name." },
          systemRole: { type: "string", enum: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"], description: "Initial system role." },
          password: { type: "string", description: "Initial password, minimum 8 characters." },
        },
        required: ["email", "name", "password"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "set_user_password",
      description: "Reset a user's password. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          userId: { type: "string", description: "User ID." },
          password: { type: "string", description: "New password, minimum 8 characters." },
        },
        required: ["userId", "password"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_user_role",
      description: "Change a user's system role. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "User ID." },
          systemRole: { type: "string", enum: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
        },
        required: ["id", "systemRole"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_user_name",
      description: "Rename a user. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "User ID." },
          name: { type: "string", description: "New display name." },
        },
        required: ["id", "name"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "link_user_resource",
      description: "Link or unlink a user to a resource. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          userId: { type: "string", description: "User ID." },
          resourceId: { type: ["string", "null"], description: "Resource ID or null to unlink." },
        },
        required: ["userId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "auto_link_users_by_email",
      description: "Auto-link all users without a resource to matching resources by email. Admin role required. Always confirm first.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "set_user_permissions",
      description: "Set explicit permission overrides for a user. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          userId: { type: "string", description: "User ID." },
          overrides: {
            type: ["object", "null"],
            properties: {
              granted: { type: "array", items: { type: "string" } },
              denied: { type: "array", items: { type: "string" } },
              chapterIds: { type: "array", items: { type: "string" } },
            },
            description: "Permission override object or null to clear.",
          },
        },
        required: ["userId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "reset_user_permissions",
      description: "Reset a user's permission overrides back to role defaults. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          userId: { type: "string", description: "User ID." },
        },
        required: ["userId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_effective_user_permissions",
      description: "Get a user's resolved permissions, role, and explicit overrides. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          userId: { type: "string", description: "User ID." },
        },
        required: ["userId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "disable_user_totp",
      description: "Disable MFA TOTP for a user as an admin override. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          userId: { type: "string", description: "User ID." },
        },
        required: ["userId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_notifications",
      description: "List recent notifications for the current user.",
      parameters: {
        type: "object",
        properties: {
          unreadOnly: { type: "boolean", description: "Only show unread. Default: false" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "mark_notification_read",
      description: "Mark one notification as read, or all unread notifications when no notificationId is provided. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          notificationId: { type: "string", description: "Notification ID. Omit to mark all unread notifications as read." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_unread_notification_count",
      description: "Count unread notifications for the current user.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "create_notification",
      description: "Create a notification or task-style notification for a specific user. Manager or admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          userId: { type: "string", description: "Target user ID." },
          type: { type: "string", description: "Notification type code." },
          title: { type: "string", description: "Title." },
          body: { type: "string", description: "Optional body text." },
          entityId: { type: "string", description: "Optional linked entity ID." },
          entityType: { type: "string", description: "Optional linked entity type." },
          category: { type: "string", enum: ["NOTIFICATION", "REMINDER", "TASK", "APPROVAL"] },
          priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"] },
          link: { type: "string", description: "Optional deep link." },
          taskStatus: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"] },
          taskAction: { type: "string", description: "Optional machine-readable task action." },
          assigneeId: { type: "string", description: "Optional assignee user ID." },
          dueDate: { type: "string", format: "date-time", description: "Optional due date." },
          channel: { type: "string", enum: ["in_app", "email", "both"] },
          senderId: { type: "string", description: "Optional sender override." },
        },
        required: ["userId", "type", "title"],
      },
    },
  },

  // ── DASHBOARD DETAIL ──
  {
    type: "function",
    function: {
      name: "get_dashboard_detail",
      description: "Get detailed dashboard data: peak allocation times, top-value resources, demand pipeline, chargeability overview.",
      parameters: {
        type: "object",
        properties: {
          section: {
            type: "string",
            description: "Which section: peak_times, top_resources, demand_pipeline, chargeability_overview, or all",
          },
        },
      },
    },
  },

  // ── PROJECT MANAGEMENT ──
  {
    type: "function",
    function: {
      name: "delete_project",
      description: "Delete a project. Only DRAFT projects can be deleted. Requires manageProjects permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID or short code" },
        },
        required: ["projectId"],
      },
    },
  },

  // ── ORG UNIT MANAGEMENT ──
  {
    type: "function",
    function: {
      name: "create_org_unit",
      description: "Create a new organizational unit. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          name: { type: "string", description: "Org unit name" },
          shortName: { type: "string", description: "Short name/code" },
          level: { type: "integer", description: "Level (5, 6, or 7)" },
          parentId: { type: "string", description: "Parent org unit ID (optional)" },
          sortOrder: { type: "integer", description: "Sort order. Default: 0" },
        },
        required: ["name", "level"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_org_unit",
      description: "Update an organizational unit. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Org unit ID" },
          name: { type: "string", description: "New name" },
          shortName: { type: "string", description: "New short name" },
          sortOrder: { type: "integer", description: "New sort order" },
          isActive: { type: "boolean", description: "Set active state" },
          parentId: { type: "string", description: "Parent org unit ID; use null to clear" },
        },
        required: ["id"],
      },
    },
  },

  // ── COVER ART ──
  {
    type: "function",
    function: {
      name: "generate_project_cover",
      description: "Generate an AI cover art image for a project. Uses the configured image provider (DALL-E or Google Gemini). The image will be stored as the project's cover. Requires manageProjects permission.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID" },
          prompt: { type: "string", description: "Optional custom prompt for the AI image generation (e.g. 'futuristic car in neon cityscape'). If not provided, a default automotive/CGI prompt is used based on the project name." },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "remove_project_cover",
      description: "Remove the cover art image from a project. Requires manageProjects permission.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID" },
        },
        required: ["projectId"],
      },
    },
  },

  // ── TASK MANAGEMENT ──
  {
    type: "function",
    function: {
      name: "list_tasks",
      description: "List tasks and approvals for the current user via the real notification router, optionally including tasks assigned to them.",
      parameters: {
        type: "object",
        properties: {
          status: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"], description: "Optional status filter." },
          includeAssigned: { type: "boolean", description: "Include tasks where the current user is assignee as well as owner. Default: true." },
          limit: { type: "integer", description: "Max results. Default: 20." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_task_counts",
      description: "Get open, in-progress, done, dismissed, and overdue task counts for the current user.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_task_detail",
      description: "Get details of a specific task/notification including linked entity information.",
      parameters: {
        type: "object",
        properties: {
          taskId: { type: "string", description: "Notification/task ID" },
        },
        required: ["taskId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_task_status",
      description: "Update the status of a task. Mark as IN_PROGRESS, DONE, or DISMISSED.",
      parameters: {
        type: "object",
        properties: {
          taskId: { type: "string", description: "Task/notification ID" },
          status: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"], description: "New status" },
        },
        required: ["taskId", "status"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "execute_task_action",
      description: "Execute the machine-readable action associated with a task. For example: approve a vacation, confirm an assignment, etc. The action is encoded in the task's taskAction field.",
      parameters: {
        type: "object",
        properties: {
          taskId: { type: "string", description: "Task/notification ID containing the action to execute" },
        },
        required: ["taskId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_reminder",
      description: "Create a personal reminder for the current user via the real notification router. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          title: { type: "string", description: "Reminder title" },
          body: { type: "string", description: "Optional details" },
          remindAt: { type: "string", format: "date-time", description: "When to remind (ISO 8601 datetime)" },
          recurrence: { type: "string", enum: ["daily", "weekly", "monthly"], description: "Optional recurrence pattern" },
          entityId: { type: "string", description: "Optional: linked entity ID (project, resource, etc.)" },
          entityType: { type: "string", description: "Optional: entity type (project, resource, vacation, etc.)" },
          link: { type: "string", description: "Optional deep link." },
        },
        required: ["title", "remindAt"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_reminders",
      description: "List personal reminders for the current user via the real notification router.",
      parameters: {
        type: "object",
        properties: {
          limit: { type: "integer", description: "Max results. Default: 20." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_reminder",
      description: "Update a personal reminder via the real notification router. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Reminder notification ID." },
          title: { type: "string", description: "Optional reminder title." },
          body: { type: "string", description: "Optional reminder body." },
          remindAt: { type: "string", format: "date-time", description: "Optional reminder timestamp." },
          recurrence: { type: ["string", "null"], enum: ["daily", "weekly", "monthly", null], description: "Optional recurrence update. Use null to clear recurrence." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "delete_reminder",
      description: "Delete a personal reminder via the real notification router. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Reminder notification ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_task_for_user",
      description: "Create a task for a specific user via the real notification router. Manager or admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          userId: { type: "string", description: "Target user ID" },
          title: { type: "string", description: "Task title" },
          body: { type: "string", description: "Task description" },
          priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"], description: "Priority (default NORMAL)" },
          dueDate: { type: "string", format: "date-time", description: "Optional due date (ISO 8601)" },
          taskAction: { type: "string", description: "Optional machine-readable action (format: action_name:entity_id)" },
          entityId: { type: "string", description: "Optional linked entity ID" },
          entityType: { type: "string", description: "Optional entity type" },
          link: { type: "string", description: "Optional deep link." },
          channel: { type: "string", enum: ["in_app", "email", "both"], description: "Delivery channel. Default: in_app." },
        },
        required: ["userId", "title"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "assign_task",
      description: "Assign or reassign a task to another user via the real notification router. Manager or admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Task notification ID." },
          assigneeId: { type: "string", description: "User ID to assign." },
        },
        required: ["id", "assigneeId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "send_broadcast",
      description: "Create and send a broadcast notification via the real notification router. Manager or admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          title: { type: "string", description: "Notification title" },
          body: { type: "string", description: "Notification body" },
          targetType: { type: "string", enum: ["user", "role", "project", "orgUnit", "all"], description: "Target audience type" },
          targetValue: { type: "string", description: "Target value: user ID, role name (ADMIN/MANAGER/CONTROLLER/USER/VIEWER), project ID, or org unit ID" },
          category: { type: "string", enum: ["NOTIFICATION", "REMINDER", "TASK", "APPROVAL"], description: "Broadcast category. Default: NOTIFICATION." },
          priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"], description: "Priority (default NORMAL)" },
          channel: { type: "string", enum: ["in_app", "email", "both"], description: "Delivery channel (default in_app)" },
          link: { type: "string", description: "Optional deep-link URL" },
          scheduledAt: { type: "string", format: "date-time", description: "Optional scheduled send timestamp." },
          taskAction: { type: "string", description: "Optional machine-readable task action for task-like broadcasts." },
          dueDate: { type: "string", format: "date-time", description: "Optional due date for task-like broadcasts." },
        },
        required: ["title", "targetType"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_broadcasts",
      description: "List notification broadcasts via the real notification router. Manager or admin role required.",
      parameters: {
        type: "object",
        properties: {
          limit: { type: "integer", description: "Max results. Default: 20." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_broadcast_detail",
      description: "Get one notification broadcast via the real notification router. Manager or admin role required.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Broadcast ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "delete_notification",
      description: "Delete one of the current user's own notifications via the real notification router. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Notification ID." },
        },
        required: ["id"],
      },
    },
  },

  // ── INSIGHTS & ANOMALIES ──
  {
    type: "function",
    function: {
      name: "detect_anomalies",
      description: "Detect anomalies across all active projects: budget burn rate issues, staffing gaps, utilization outliers, and timeline overruns.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_skill_gaps",
      description: "Analyze skill supply vs demand across all active projects. Returns which skills are in short supply relative to demand requirements.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_project_health",
      description: "Get health scores for all active projects based on budget utilization, staffing completeness, and timeline status.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_budget_forecast",
      description: "Get budget utilization and burn rate per active project. Shows total budget, spent, remaining, and whether burn is ahead or behind schedule.",
      parameters: { type: "object", properties: {} },
    },
  },
  {
    type: "function",
    function: {
      name: "get_insights_summary",
      description: "Get a summary of anomaly counts by category (budget, staffing, timeline, utilization) plus critical count.",
      parameters: { type: "object", properties: {} },
    },
  },

  // ── REPORTS & COMMENTS ──
  {
    type: "function",
    function: {
      name: "run_report",
      description: "Run a dynamic report query on resources, projects, assignments, or resource-month rows with flexible column selection and filtering.",
      parameters: {
        type: "object",
        properties: {
          entity: {
            type: "string",
            enum: ["resource", "project", "assignment", "resource_month"],
            description: "Entity type to query",
          },
          columns: {
            type: "array",
            items: { type: "string" },
            description: "Column keys to include (e.g. 'displayName', 'chapter', 'country.name')",
          },
          filters: {
            type: "array",
            items: {
              type: "object",
              properties: {
                field: { type: "string", description: "Field to filter on" },
                op: { type: "string", enum: ["eq", "neq", "gt", "lt", "gte", "lte", "contains", "in"], description: "Filter operator" },
                value: { type: "string", description: "Filter value (string)" },
              },
              required: ["field", "op", "value"],
            },
            description: "Filters to apply",
          },
          periodMonth: {
            type: "string",
            description: "Required for resource_month reports. Format: YYYY-MM",
          },
          limit: { type: "integer", description: "Max results. Default: 50" },
        },
        required: ["entity", "columns"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_comments",
      description: "List comments (with replies) for a specific entity such as an estimate, scope item, or demand line.",
      parameters: {
        type: "object",
        properties: {
          entityType: { type: "string", description: "Entity type (e.g. 'estimate', 'estimate_version', 'scope_item', 'demand_line')" },
          entityId: { type: "string", description: "Entity ID" },
        },
        required: ["entityType", "entityId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "lookup_rate",
      description: "Find the best matching rate card line for given criteria (client, chapter, management level, role, seniority).",
      parameters: {
        type: "object",
        properties: {
          clientId: { type: "string", description: "Client ID to find rate card for" },
          chapter: { type: "string", description: "Chapter to match" },
          managementLevelId: { type: "string", description: "Management level ID to match" },
          roleName: { type: "string", description: "Role name to match" },
          seniority: { type: "string", description: "Seniority level to match" },
        },
      },
    },
  },

  // ── SCENARIO & AI ──
  {
    type: "function",
    function: {
      name: "simulate_scenario",
      description: "Run a read-only what-if staffing simulation for a project. Shows cost/hours/utilization impact of adding, removing, or changing resource assignments without persisting changes.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID" },
          changes: {
            type: "array",
            items: {
              type: "object",
              properties: {
                assignmentId: { type: "string", description: "Existing assignment ID to modify (omit for new)" },
                resourceId: { type: "string", description: "Resource ID" },
                roleId: { type: "string", description: "Role ID" },
                startDate: { type: "string", description: "Start date YYYY-MM-DD" },
                endDate: { type: "string", description: "End date YYYY-MM-DD" },
                hoursPerDay: { type: "number", description: "Hours per day" },
                remove: { type: "boolean", description: "Set true to remove an existing assignment" },
              },
              required: ["startDate", "endDate", "hoursPerDay"],
            },
            description: "Array of staffing changes to simulate",
          },
        },
        required: ["projectId", "changes"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "generate_project_narrative",
      description: "Generate an AI-powered executive narrative for a project covering budget, staffing, timeline risk, and action items. Requires AI to be configured.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID" },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_comment",
      description: "Add a comment to an entity (estimate, scope item, demand line, etc.). Supports @mentions. Always confirm with the user first.",
      parameters: {
        type: "object",
        properties: {
          entityType: { type: "string", description: "Entity type (e.g. 'estimate', 'estimate_version', 'scope_item')" },
          entityId: { type: "string", description: "Entity ID" },
          body: { type: "string", description: "Comment body text. Use @[Name](userId) for mentions." },
        },
        required: ["entityType", "entityId", "body"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "resolve_comment",
      description: "Mark a comment as resolved (or unresolve it). Only the comment author or an admin can do this.",
      parameters: {
        type: "object",
        properties: {
          commentId: { type: "string", description: "Comment ID to resolve" },
          resolved: { type: "boolean", description: "Set to true to resolve, false to unresolve. Default: true" },
        },
        required: ["commentId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "query_change_history",
      description: "Search the audit history for changes to projects, resources, allocations, vacations, or any entity. Reuses the real audit log list API. Controller/manager/admin roles only.",
      parameters: {
        type: "object",
        properties: {
          entityType: { type: "string", description: "Filter by entity type (e.g. 'Project', 'Resource', 'Allocation', 'Vacation', 'Role', 'Estimate')" },
          search: { type: "string", description: "Search in entity name or summary text" },
          userId: { type: "string", description: "Filter by user ID who made the change" },
          daysBack: { type: "integer", description: "How many days back to search. Default: 7" },
          action: { type: "string", description: "Filter by action type: CREATE, UPDATE, DELETE, SHIFT, IMPORT" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_entity_timeline",
      description: "Get the audit history for a specific entity (project, resource, etc.) via the real audit API. Controller/manager/admin roles only.",
      parameters: {
        type: "object",
        properties: {
          entityType: { type: "string", description: "Entity type (e.g. 'Project', 'Resource', 'Allocation')" },
          entityId: { type: "string", description: "Entity ID" },
          limit: { type: "integer", description: "Max results. Default: 50" },
        },
        required: ["entityType", "entityId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "export_resources_csv",
      description: "Export the current active resource list as CSV via the real import/export router. Controller/manager/admin roles only.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "export_projects_csv",
      description: "Export the current project list as CSV via the real import/export router. Controller/manager/admin roles only.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "import_csv_data",
      description: "Import CSV-style row data for resources, projects, or allocations via the real import/export router. Requires manager/admin, importData permission, and confirmation.",
      parameters: {
        type: "object",
        properties: {
          entityType: { type: "string", enum: ["resources", "projects", "allocations"], description: "Import target entity type." },
          rows: {
            type: "array",
            description: "CSV rows already parsed to key/value objects.",
            items: {
              type: "object",
              additionalProperties: { type: "string" },
            },
          },
          dryRun: { type: "boolean", description: "Validate only without persisting changes. Default: true." },
        },
        required: ["entityType", "rows"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_dispo_import_batches",
      description: "List Dispo import batches with pagination and optional status filter via the real dispo router. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          status: { type: "string", description: "Optional batch status filter." },
          limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
          cursor: { type: "string", description: "Optional pagination cursor." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_dispo_import_batch",
      description: "Get one Dispo import batch including staged record counters via the real dispo router. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Import batch ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "stage_dispo_import_batch",
      description: "Stage a Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          planningWorkbookPath: { type: "string", description: "Filesystem path to the planning workbook." },
          referenceWorkbookPath: { type: "string", description: "Filesystem path to the reference workbook." },
          chargeabilityWorkbookPath: { type: "string", description: "Filesystem path to the chargeability workbook." },
          costWorkbookPath: { type: "string", description: "Optional filesystem path to the cost workbook." },
          rosterWorkbookPath: { type: "string", description: "Optional filesystem path to the roster workbook." },
          notes: { type: "string", description: "Optional import notes." },
        },
        required: ["planningWorkbookPath", "referenceWorkbookPath", "chargeabilityWorkbookPath"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "validate_dispo_import_batch",
      description: "Validate a Dispo import batch readiness check via the real dispo router without committing anything. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          planningWorkbookPath: { type: "string", description: "Filesystem path to the planning workbook." },
          referenceWorkbookPath: { type: "string", description: "Filesystem path to the reference workbook." },
          chargeabilityWorkbookPath: { type: "string", description: "Filesystem path to the chargeability workbook." },
          costWorkbookPath: { type: "string", description: "Optional filesystem path to the cost workbook." },
          rosterWorkbookPath: { type: "string", description: "Optional filesystem path to the roster workbook." },
          importBatchId: { type: "string", description: "Optional existing staged import batch ID." },
          notes: { type: "string", description: "Optional import notes." },
        },
        required: ["planningWorkbookPath", "referenceWorkbookPath", "chargeabilityWorkbookPath"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "cancel_dispo_import_batch",
      description: "Cancel a staged Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Import batch ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_dispo_staged_resources",
      description: "List staged Dispo resources for one import batch via the real dispo router. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          importBatchId: { type: "string", description: "Import batch ID." },
          status: { type: "string", description: "Optional staged record status filter." },
          limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
          cursor: { type: "string", description: "Optional pagination cursor." },
        },
        required: ["importBatchId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_dispo_staged_projects",
      description: "List staged Dispo projects for one import batch via the real dispo router. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          importBatchId: { type: "string", description: "Import batch ID." },
          status: { type: "string", description: "Optional staged record status filter." },
          isTbd: { type: "boolean", description: "Optional TBD-project filter." },
          limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
          cursor: { type: "string", description: "Optional pagination cursor." },
        },
        required: ["importBatchId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_dispo_staged_assignments",
      description: "List staged Dispo assignments for one import batch via the real dispo router. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          importBatchId: { type: "string", description: "Import batch ID." },
          status: { type: "string", description: "Optional staged record status filter." },
          resourceExternalId: { type: "string", description: "Optional resource external ID filter." },
          limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
          cursor: { type: "string", description: "Optional pagination cursor." },
        },
        required: ["importBatchId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_dispo_staged_vacations",
      description: "List staged Dispo vacations for one import batch via the real dispo router. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          importBatchId: { type: "string", description: "Import batch ID." },
          resourceExternalId: { type: "string", description: "Optional resource external ID filter." },
          limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
          cursor: { type: "string", description: "Optional pagination cursor." },
        },
        required: ["importBatchId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_dispo_staged_unresolved_records",
      description: "List staged unresolved Dispo records for one import batch via the real dispo router. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          importBatchId: { type: "string", description: "Import batch ID." },
          recordType: { type: "string", description: "Optional unresolved record type filter." },
          limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
          cursor: { type: "string", description: "Optional pagination cursor." },
        },
        required: ["importBatchId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "resolve_dispo_staged_record",
      description: "Resolve one staged Dispo record via the real dispo router. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Staged record ID." },
          recordType: { type: "string", description: "Staged record type." },
          action: { type: "string", enum: ["APPROVE", "REJECT", "SKIP"], description: "Resolution action." },
        },
        required: ["id", "recordType", "action"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "commit_dispo_import_batch",
      description: "Commit a staged Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          importBatchId: { type: "string", description: "Import batch ID." },
          allowTbdUnresolved: { type: "boolean", description: "Allow unresolved TBD projects during commit." },
          importTbdProjects: { type: "boolean", description: "Whether TBD projects should be imported." },
        },
        required: ["importBatchId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_system_settings",
      description: "Get sanitized system settings through the real settings router. Admin role required.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_system_settings",
      description: "Update system settings through the real settings router. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          aiProvider: { type: "string", enum: ["openai", "azure"] },
          azureOpenAiEndpoint: { type: "string" },
          azureOpenAiDeployment: { type: "string" },
          azureOpenAiApiKey: { type: "string" },
          azureApiVersion: { type: "string" },
          aiMaxCompletionTokens: { type: "integer" },
          aiTemperature: { type: "number" },
          aiSummaryPrompt: { type: "string" },
          scoreWeights: { type: "object" },
          scoreVisibleRoles: { type: "array", items: { type: "string" } },
          smtpHost: { type: "string" },
          smtpPort: { type: "integer" },
          smtpUser: { type: "string" },
          smtpPassword: { type: "string" },
          smtpFrom: { type: "string" },
          smtpTls: { type: "boolean" },
          anonymizationEnabled: { type: "boolean" },
          anonymizationDomain: { type: "string" },
          anonymizationSeed: { type: "string" },
          anonymizationMode: { type: "string", enum: ["global"] },
          azureDalleDeployment: { type: "string" },
          azureDalleEndpoint: { type: "string" },
          azureDalleApiKey: { type: "string" },
          geminiApiKey: { type: "string" },
          geminiModel: { type: "string" },
          imageProvider: { type: "string", enum: ["dalle", "gemini"] },
          vacationDefaultDays: { type: "integer" },
          timelineUndoMaxSteps: { type: "integer" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "test_ai_connection",
      description: "Run the real AI connection test from system settings. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "test_smtp_connection",
      description: "Run the real SMTP connection test from system settings. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "test_gemini_connection",
      description: "Run the real Gemini connection test from system settings. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_ai_configured",
      description: "Get whether AI is configured for the current system via the real settings router. Available to any authenticated user.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_system_role_configs",
      description: "List system role configuration defaults via the real system-role-config router. Available to any authenticated user.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_system_role_config",
      description: "Update one system role configuration via the real system-role-config router. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          role: { type: "string", description: "System role key." },
          label: { type: "string", description: "Optional role label." },
          description: { type: "string", description: "Optional role description." },
          color: { type: "string", description: "Optional role color." },
          defaultPermissions: { type: "array", items: { type: "string" }, description: "Optional default permission set." },
        },
        required: ["role"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_webhooks",
      description: "List webhooks via the real webhook router. Secrets are masked in assistant responses. Admin role required.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_webhook",
      description: "Get one webhook via the real webhook router. Secrets are masked in assistant responses. Admin role required.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Webhook ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_webhook",
      description: "Create a webhook via the real webhook router. Secrets are masked in assistant responses. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          name: { type: "string", description: "Webhook name." },
          url: { type: "string", description: "Webhook target URL." },
          secret: { type: "string", description: "Optional webhook signing secret." },
          events: { type: "array", items: { type: "string" }, description: "Subscribed webhook events." },
          isActive: { type: "boolean", description: "Whether the webhook is active. Default: true." },
        },
        required: ["name", "url", "events"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "update_webhook",
      description: "Update a webhook via the real webhook router. Secrets are masked in assistant responses. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Webhook ID." },
          data: {
            type: "object",
            properties: {
              name: { type: "string" },
              url: { type: "string" },
              secret: { type: "string" },
              events: { type: "array", items: { type: "string" } },
              isActive: { type: "boolean" },
            },
          },
        },
        required: ["id", "data"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "delete_webhook",
      description: "Delete a webhook via the real webhook router. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Webhook ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "test_webhook",
      description: "Send a real test payload to a webhook via the real webhook router. Admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Webhook ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "list_audit_log_entries",
      description: "List audit log entries with full audit-router filters and cursor pagination. Controller/manager/admin roles only.",
      parameters: {
        type: "object",
        properties: {
          entityType: { type: "string", description: "Optional entity type filter." },
          entityId: { type: "string", description: "Optional entity ID filter." },
          userId: { type: "string", description: "Optional user ID filter." },
          action: { type: "string", description: "Optional action filter such as CREATE, UPDATE, DELETE, SHIFT, IMPORT." },
          source: { type: "string", description: "Optional source filter such as ui or assistant." },
          startDate: { type: "string", description: "Optional start date in YYYY-MM-DD." },
          endDate: { type: "string", description: "Optional end date in YYYY-MM-DD." },
          search: { type: "string", description: "Optional case-insensitive search across entity name, summary, and entity type." },
          limit: { type: "integer", description: "Max results. Default: 50, max: 100." },
          cursor: { type: "string", description: "Optional pagination cursor (last seen audit entry ID)." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_audit_log_entry",
      description: "Get one audit log entry including the full changes payload. Controller/manager/admin roles only.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Audit log entry ID." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_audit_log_timeline",
      description: "Get audit log entries grouped by day for a time window. Controller/manager/admin roles only.",
      parameters: {
        type: "object",
        properties: {
          startDate: { type: "string", description: "Optional start date in YYYY-MM-DD." },
          endDate: { type: "string", description: "Optional end date in YYYY-MM-DD." },
          limit: { type: "integer", description: "Max entries. Default: 200, max: 500." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_audit_activity_summary",
      description: "Get audit activity totals by entity type, action, and user for a date range. Controller/manager/admin roles only.",
      parameters: {
        type: "object",
        properties: {
          startDate: { type: "string", description: "Optional start date in YYYY-MM-DD." },
          endDate: { type: "string", description: "Optional end date in YYYY-MM-DD." },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_shoring_ratio",
      description: "Get the onshore/offshore staffing ratio for a project. Higher offshore is better (cost-efficient). The threshold is the MINIMUM offshore target. Shows country breakdown and whether the target is met.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID or short code" },
        },
        required: ["projectId"],
      },
    },
  },
];

// ─── Helpers ────────────────────────────────────────────────────────────────

/** Resolve a responsible person name against existing resources. Returns the exact displayName or an error object. */
async function resolveResponsiblePerson(
  name: string,
  ctx: ToolContext,
): Promise<{ displayName: string } | { error: string }> {
  const caller = createResourceCaller(createScopedCallerContext(ctx));
  const result = await caller.resolveResponsiblePersonName({ name });

  if (result.status === "resolved") {
    return { displayName: result.displayName };
  }

  if (result.status === "ambiguous" || result.status === "missing") {
    return { error: result.message };
  }

  return { error: `Unable to resolve responsible person: ${name}` };
}

// ─── Tool Executors ─────────────────────────────────────────────────────────

const executors = {
  async search_resources(params: {
    query?: string; country?: string; metroCity?: string;
    orgUnit?: string; roleName?: string;
    isActive?: boolean; limit?: number;
  }, ctx: ToolContext) {
    const caller = createResourceCaller(createScopedCallerContext(ctx));
    return caller.listSummariesDetail({
      search: params.query,
      country: params.country,
      metroCity: params.metroCity,
      orgUnit: params.orgUnit,
      roleName: params.roleName,
      isActive: params.isActive ?? true,
      limit: Math.min(params.limit ?? 50, 100),
    });
  },

  async get_resource(params: { identifier: string }, ctx: ToolContext) {
    const caller = createResourceCaller(createScopedCallerContext(ctx));
    return caller.getByIdentifierDetail({ identifier: params.identifier });
  },

  async search_projects(params: { query?: string; status?: string; limit?: number }, ctx: ToolContext) {
    const caller = createProjectCaller(createScopedCallerContext(ctx));
    return caller.searchSummariesDetail({
      search: params.query,
      status: params.status as import("@capakraken/shared").ProjectStatus | undefined,
      limit: Math.min(params.limit ?? 20, 50),
    });
  },

  async get_project(params: { identifier: string }, ctx: ToolContext) {
    const caller = createProjectCaller(createScopedCallerContext(ctx));
    let project;
    try {
      project = await caller.getByIdentifierDetail({ identifier: params.identifier });
    } catch {
      return { error: `Project not found: ${params.identifier}` };
    }
    return project;
  },

  async find_best_project_resource(params: {
    projectIdentifier: string;
    startDate?: string;
    endDate?: string;
    durationDays?: number;
    minHoursPerDay?: number;
    rankingMode?: "lowest_lcr" | "highest_remaining_hours_per_day" | "highest_remaining_hours";
    chapter?: string;
    roleName?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);
    assertPermission(ctx, PermissionKey.VIEW_COSTS);

    const project = await resolveProjectIdentifier(ctx, params.projectIdentifier);
    if ("error" in project) {
      return project;
    }

    const caller = createStaffingCaller(createScopedCallerContext(ctx));
    return caller.getBestProjectResourceDetail({
      projectId: project.id,
      ...(params.startDate ? { startDate: parseIsoDate(params.startDate, "startDate") } : {}),
      ...(params.endDate ? { endDate: parseIsoDate(params.endDate, "endDate") } : {}),
      ...(params.durationDays !== undefined ? { durationDays: params.durationDays } : {}),
      ...(params.minHoursPerDay !== undefined ? { minHoursPerDay: params.minHoursPerDay } : {}),
      ...(params.rankingMode ? { rankingMode: params.rankingMode } : {}),
      ...(params.chapter ? { chapter: params.chapter } : {}),
      ...(params.roleName ? { roleName: params.roleName } : {}),
    });
  },

  async get_timeline_entries_view(params: {
    startDate?: string;
    endDate?: string;
    durationDays?: number;
    resourceIds?: string[];
    projectIds?: string[];
    clientIds?: string[];
    chapters?: string[];
    eids?: string[];
    countryCodes?: string[];
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    return caller.getEntriesDetail({ ...params });
  },

  async get_timeline_holiday_overlays(params: {
    startDate?: string;
    endDate?: string;
    durationDays?: number;
    resourceIds?: string[];
    projectIds?: string[];
    clientIds?: string[];
    chapters?: string[];
    eids?: string[];
    countryCodes?: string[];
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    return caller.getHolidayOverlayDetail({ ...params });
  },

  async get_project_timeline_context(params: {
    projectIdentifier: string;
    startDate?: string;
    endDate?: string;
    durationDays?: number;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const project = await resolveProjectIdentifier(ctx, params.projectIdentifier);
    if ("error" in project) {
      return project;
    }

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    return caller.getProjectContextDetail({
      projectId: project.id,
      ...(params.startDate ? { startDate: params.startDate } : {}),
      ...(params.endDate ? { endDate: params.endDate } : {}),
      ...(params.durationDays !== undefined ? { durationDays: params.durationDays } : {}),
    });
  },

  async preview_project_shift(params: {
    projectIdentifier: string;
    newStartDate: string;
    newEndDate: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const project = await resolveProjectIdentifier(ctx, params.projectIdentifier);
    if ("error" in project) {
      return project;
    }

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    return caller.getShiftPreviewDetail({
      projectId: project.id,
      newStartDate: parseIsoDate(params.newStartDate, "newStartDate"),
      newEndDate: parseIsoDate(params.newEndDate, "newEndDate"),
    });
  },

  async update_timeline_allocation_inline(params: {
    allocationId: string;
    hoursPerDay?: number;
    startDate?: string;
    endDate?: string;
    includeSaturday?: boolean;
    role?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    const updated = await caller.updateAllocationInline({
      allocationId: params.allocationId,
      ...(params.hoursPerDay !== undefined ? { hoursPerDay: params.hoursPerDay } : {}),
      ...(params.startDate ? { startDate: parseIsoDate(params.startDate, "startDate") } : {}),
      ...(params.endDate ? { endDate: parseIsoDate(params.endDate, "endDate") } : {}),
      ...(params.includeSaturday !== undefined ? { includeSaturday: params.includeSaturday } : {}),
      ...(params.role !== undefined ? { role: params.role } : {}),
    });

    return {
      __action: "invalidate",
      scope: ["allocation", "timeline", "project"],
      success: true,
      message: `Updated timeline allocation ${updated.id}.`,
      allocation: {
        id: updated.id,
        projectId: updated.projectId,
        resourceId: updated.resourceId ?? null,
        startDate: fmtDate(updated.startDate),
        endDate: fmtDate(updated.endDate),
        hoursPerDay: updated.hoursPerDay,
        role: updated.role ?? null,
        status: updated.status,
      },
    };
  },

  async apply_timeline_project_shift(params: {
    projectIdentifier: string;
    newStartDate: string;
    newEndDate: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const project = await resolveProjectIdentifier(ctx, params.projectIdentifier);
    if ("error" in project) {
      return project;
    }

    const newStartDate = parseIsoDate(params.newStartDate, "newStartDate");
    const newEndDate = parseIsoDate(params.newEndDate, "newEndDate");
    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    const result = await caller.applyShift({
      projectId: project.id,
      newStartDate,
      newEndDate,
    });

    return {
      __action: "invalidate",
      scope: ["allocation", "timeline", "project"],
      success: true,
      message: `Shifted project ${project.shortCode ?? project.name ?? project.id} to ${fmtDate(newStartDate)} - ${fmtDate(newEndDate)}.`,
      project: {
        id: result.project.id,
        startDate: fmtDate(result.project.startDate),
        endDate: fmtDate(result.project.endDate),
      },
      validation: result.validation,
    };
  },

  async quick_assign_timeline_resource(params: {
    resourceIdentifier: string;
    projectIdentifier: string;
    startDate: string;
    endDate: string;
    hoursPerDay?: number;
    role?: string;
    roleId?: string;
    status?: AllocationStatus;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const [resource, project] = await Promise.all([
      resolveResourceIdentifier(ctx, params.resourceIdentifier),
      resolveProjectIdentifier(ctx, params.projectIdentifier),
    ]);
    if ("error" in resource) {
      return resource;
    }
    if ("error" in project) {
      return project;
    }

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    const allocation = await caller.quickAssign({
      resourceId: resource.id,
      projectId: project.id,
      startDate: parseIsoDate(params.startDate, "startDate"),
      endDate: parseIsoDate(params.endDate, "endDate"),
      ...(params.hoursPerDay !== undefined ? { hoursPerDay: params.hoursPerDay } : {}),
      ...(params.role !== undefined ? { role: params.role } : {}),
      ...(params.roleId !== undefined ? { roleId: params.roleId } : {}),
      ...(params.status !== undefined ? { status: params.status } : {}),
    });

    return {
      __action: "invalidate",
      scope: ["allocation", "timeline", "project"],
      success: true,
      message: `Quick-assigned ${resource.displayName} to ${project.name} (${project.shortCode ?? project.id}).`,
      allocation: {
        id: allocation.id,
        projectId: allocation.projectId,
        resourceId: allocation.resourceId ?? null,
        startDate: fmtDate(toDate(allocation.startDate)),
        endDate: fmtDate(toDate(allocation.endDate)),
        hoursPerDay: allocation.hoursPerDay,
        role: allocation.role ?? null,
        status: allocation.status,
      },
    };
  },

  async batch_quick_assign_timeline_resources(params: {
    assignments: Array<{
      resourceIdentifier: string;
      projectIdentifier: string;
      startDate: string;
      endDate: string;
      hoursPerDay?: number;
      role?: string;
      status?: AllocationStatus;
    }>;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const resolvedAssignments = await Promise.all(params.assignments.map(async (assignment, index) => {
      const [resource, project] = await Promise.all([
        resolveResourceIdentifier(ctx, assignment.resourceIdentifier),
        resolveProjectIdentifier(ctx, assignment.projectIdentifier),
      ]);
      if ("error" in resource) {
        throw new Error(`assignments[${index}].resourceIdentifier: ${resource.error}`);
      }
      if ("error" in project) {
        throw new Error(`assignments[${index}].projectIdentifier: ${project.error}`);
      }
      return {
        resourceId: resource.id,
        projectId: project.id,
        startDate: parseIsoDate(assignment.startDate, `assignments[${index}].startDate`),
        endDate: parseIsoDate(assignment.endDate, `assignments[${index}].endDate`),
        ...(assignment.hoursPerDay !== undefined ? { hoursPerDay: assignment.hoursPerDay } : {}),
        ...(assignment.role !== undefined ? { role: assignment.role } : {}),
        ...(assignment.status !== undefined ? { status: assignment.status } : {}),
      };
    }));

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    const result = await caller.batchQuickAssign({
      assignments: resolvedAssignments,
    });

    return {
      __action: "invalidate",
      scope: ["allocation", "timeline", "project"],
      success: true,
      message: `Created ${result.count} timeline quick-assignment(s).`,
      count: result.count,
    };
  },

  async batch_shift_timeline_allocations(params: {
    allocationIds: string[];
    daysDelta: number;
    mode?: "move" | "resize-start" | "resize-end";
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    const result = await caller.batchShiftAllocations({
      allocationIds: params.allocationIds,
      daysDelta: params.daysDelta,
      ...(params.mode !== undefined ? { mode: params.mode } : {}),
    });

    return {
      __action: "invalidate",
      scope: ["allocation", "timeline", "project"],
      success: true,
      message: `Shifted ${result.count} allocation(s) by ${params.daysDelta} day(s).`,
      count: result.count,
    };
  },

  async list_allocations(params: {
    resourceId?: string; projectId?: string;
    resourceName?: string; projectCode?: string;
    status?: string; limit?: number;
  }, ctx: ToolContext) {
    const caller = createAllocationCaller(createScopedCallerContext(ctx));
    const status = params.status && Object.values(AllocationStatus).includes(params.status as AllocationStatus)
      ? params.status as AllocationStatus
      : undefined;
    const readModel = await caller.listView({
      resourceId: params.resourceId,
      projectId: params.projectId,
      status,
    });

    const resourceNameQuery = params.resourceName?.trim().toLowerCase();
    const projectCodeQuery = params.projectCode?.trim().toLowerCase();
    const limit = Math.min(params.limit ?? 30, 50);

    return readModel.assignments
      .filter((assignment) => {
        if (
          resourceNameQuery
          && !assignment.resource?.displayName?.toLowerCase().includes(resourceNameQuery)
        ) {
          return false;
        }
        if (
          projectCodeQuery
          && !assignment.project?.shortCode?.toLowerCase().includes(projectCodeQuery)
        ) {
          return false;
        }
        return true;
      })
      .slice(0, limit)
      .map((assignment) => ({
        id: assignment.id,
        resource: assignment.resource?.displayName ?? "Unknown",
        resourceEid: assignment.resource?.eid ?? null,
        project: assignment.project?.name ?? "Unknown",
        projectCode: assignment.project?.shortCode ?? null,
        role: assignment.role ?? assignment.roleEntity?.name ?? null,
        status: assignment.status,
        hoursPerDay: assignment.hoursPerDay,
        dailyCost: fmtEur(assignment.dailyCostCents),
        start: fmtDate(new Date(assignment.startDate)),
        end: fmtDate(new Date(assignment.endDate)),
      }));
  },

  async get_budget_status(params: { projectId: string }, ctx: ToolContext) {
    const project = await resolveProjectIdentifier(ctx, params.projectId);
    if ("error" in project) {
      return project;
    }

    const caller = createTimelineCaller(createScopedCallerContext(ctx));
    const budgetStatus = await caller.getBudgetStatus({ projectId: project.id });

    if (budgetStatus.budgetCents <= 0) {
      return {
        project: budgetStatus.projectName,
        code: budgetStatus.projectCode,
        budget: "Not set",
        note: "No budget defined for this project",
        totalAllocations: budgetStatus.totalAllocations,
      };
    }

    return {
      project: budgetStatus.projectName,
      code: budgetStatus.projectCode,
      budget: fmtEur(budgetStatus.budgetCents),
      confirmed: fmtEur(budgetStatus.confirmedCents),
      proposed: fmtEur(budgetStatus.proposedCents),
      allocated: fmtEur(budgetStatus.allocatedCents),
      remaining: fmtEur(budgetStatus.remainingCents),
      utilization: `${budgetStatus.utilizationPercent.toFixed(1)}%`,
      winWeighted: fmtEur(budgetStatus.winProbabilityWeightedCents),
    };
  },

  async get_vacation_balance(params: { resourceId: string; year?: number }, ctx: ToolContext) {
    const year = params.year ?? new Date().getFullYear();
    const resource = await resolveResourceIdentifier(ctx, params.resourceId);
    if ("error" in resource) return resource;

    const caller = createEntitlementCaller(createScopedCallerContext(ctx));
    return caller.getBalanceDetail({ resourceId: resource.id, year });
  },

  async list_vacations_upcoming(params: {
    resourceName?: string; chapter?: string; daysAhead?: number; limit?: number;
  }, ctx: ToolContext) {
    const daysAhead = params.daysAhead ?? 30;
    const limit = Math.min(params.limit ?? 30, 50);
    const caller = createVacationCaller(createScopedCallerContext(ctx));
    const now = new Date();
    const until = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000);

    const vacations = await caller.list({
      status: "APPROVED",
      startDate: now,
      endDate: until,
      limit,
    });

    const filtered = vacations
      .filter((vacation) => {
        if (params.resourceName) {
          const resourceName = vacation.resource?.displayName?.toLowerCase() ?? "";
          if (!resourceName.includes(params.resourceName.toLowerCase())) {
            return false;
          }
        }
        if (params.chapter) {
          const chapter = vacation.resource?.chapter?.toLowerCase() ?? "";
          if (!chapter.includes(params.chapter.toLowerCase())) {
            return false;
          }
        }
        return true;
      })
      .slice(0, limit);

    return filtered.map((v) => ({
      resource: v.resource.displayName,
      eid: v.resource.eid,
      chapter: v.resource.chapter ?? null,
      type: v.type,
      start: fmtDate(v.startDate),
      end: fmtDate(v.endDate),
      isHalfDay: v.isHalfDay,
      halfDayPart: v.halfDayPart,
    }));
  },

  async list_holidays_by_region(params: {
    countryCode: string;
    federalState?: string;
    metroCity?: string;
    year?: number;
    periodStart?: string;
    periodEnd?: string;
  }, ctx: ToolContext) {
    const { year, periodStart, periodEnd } = resolveHolidayPeriod(params);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    const resolved = await caller.resolveHolidaysDetail({
      periodStart,
      periodEnd,
      countryCode: params.countryCode.trim().toUpperCase(),
      ...(params.federalState ? { stateCode: params.federalState } : {}),
      ...(params.metroCity ? { metroCityName: params.metroCity } : {}),
    });

    return {
      locationContext: resolved.locationContext,
      year,
      periodStart: resolved.periodStart,
      periodEnd: resolved.periodEnd,
      count: resolved.count,
      summary: resolved.summary,
      holidays: resolved.holidays,
    };
  },

  async get_resource_holidays(params: {
    identifier: string;
    year?: number;
    periodStart?: string;
    periodEnd?: string;
  }, ctx: ToolContext) {
    const resource = await resolveResourceIdentifier(ctx, params.identifier);
    if ("error" in resource) return resource;

    const { year, periodStart, periodEnd } = resolveHolidayPeriod(params);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    const resolved = await caller.resolveResourceHolidaysDetail({
      resourceId: resource.id,
      periodStart,
      periodEnd,
    });

    return {
      resource: resolved.resource,
      year,
      periodStart: resolved.periodStart,
      periodEnd: resolved.periodEnd,
      count: resolved.count,
      summary: resolved.summary,
      holidays: resolved.holidays,
    };
  },

  async list_holiday_calendars(params: {
    includeInactive?: boolean;
    countryCode?: string;
    scopeType?: "COUNTRY" | "STATE" | "CITY";
    stateCode?: string;
    metroCity?: string;
  }, ctx: ToolContext) {
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    return caller.listCalendarsDetail(params);
  },

  async get_holiday_calendar(params: {
    identifier: string;
  }, ctx: ToolContext) {
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    return caller.getCalendarByIdentifierDetail({ identifier: params.identifier.trim() });
  },

  async preview_resolved_holiday_calendar(params: {
    countryId: string;
    stateCode?: string;
    metroCityId?: string;
    year: number;
  }, ctx: ToolContext) {
    const input = PreviewResolvedHolidaysSchema.parse(params);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    return caller.previewResolvedHolidaysDetail(input);
  },

  async create_holiday_calendar(params: {
    name: string;
    scopeType: "COUNTRY" | "STATE" | "CITY";
    countryId: string;
    stateCode?: string;
    metroCityId?: string;
    isActive?: boolean;
    priority?: number;
  }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    const created = await caller.createCalendar(CreateHolidayCalendarSchema.parse(params));

    return {
      __action: "invalidate",
      scope: ["holidayCalendar", "vacation"],
      success: true,
      calendar: formatHolidayCalendar(created),
      message: `Created holiday calendar: ${created.name}`,
    };
  },

  async update_holiday_calendar(params: {
    id: string;
    data: {
      name?: string;
      stateCode?: string | null;
      metroCityId?: string | null;
      isActive?: boolean;
      priority?: number;
    };
  }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    const input = {
      id: params.id,
      data: UpdateHolidayCalendarSchema.parse(params.data),
    };
    const updated = await caller.updateCalendar(input);

    return {
      __action: "invalidate",
      scope: ["holidayCalendar", "vacation"],
      success: true,
      calendar: formatHolidayCalendar(updated),
      message: `Updated holiday calendar: ${updated.name}`,
    };
  },

  async delete_holiday_calendar(params: {
    id: string;
  }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    const deleted = await caller.deleteCalendar({ id: params.id });

    return {
      __action: "invalidate",
      scope: ["holidayCalendar", "vacation"],
      success: true,
      message: `Deleted holiday calendar: ${deleted.name}`,
    };
  },

  async create_holiday_calendar_entry(params: {
    holidayCalendarId: string;
    date: string;
    name: string;
    isRecurringAnnual?: boolean;
    source?: string;
  }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    const created = await caller.createEntry(CreateHolidayCalendarEntrySchema.parse(params));

    return {
      __action: "invalidate",
      scope: ["holidayCalendar", "vacation"],
      success: true,
      entry: formatHolidayCalendarEntry(created),
      message: `Created holiday entry: ${created.name}`,
    };
  },

  async update_holiday_calendar_entry(params: {
    id: string;
    data: {
      date?: string;
      name?: string;
      isRecurringAnnual?: boolean;
      source?: string | null;
    };
  }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    const input = {
      id: params.id,
      data: UpdateHolidayCalendarEntrySchema.parse(params.data),
    };
    const updated = await caller.updateEntry(input);

    return {
      __action: "invalidate",
      scope: ["holidayCalendar", "vacation"],
      success: true,
      entry: formatHolidayCalendarEntry(updated),
      message: `Updated holiday entry: ${updated.name}`,
    };
  },

  async delete_holiday_calendar_entry(params: {
    id: string;
  }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createHolidayCalendarCaller(createScopedCallerContext(ctx));
    const deleted = await caller.deleteEntry({ id: params.id });

    return {
      __action: "invalidate",
      scope: ["holidayCalendar", "vacation"],
      success: true,
      message: `Deleted holiday entry: ${deleted.name}`,
    };
  },

  async list_roles(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createRoleCaller(createScopedCallerContext(ctx));
    const roles = await caller.list({});
    return roles.map((role) => ({
      id: role.id,
      name: role.name,
      color: role.color,
    }));
  },

  async search_by_skill(params: { skill: string }, ctx: ToolContext) {
    const caller = createResourceCaller(createScopedCallerContext(ctx));
    const matched = await caller.searchBySkills({
      rules: [{ skill: params.skill, minProficiency: 1 }],
      operator: "OR",
    });

    return matched.slice(0, 20).map((resource) => ({
      id: resource.id,
      eid: resource.eid,
      name: resource.displayName,
      matchedSkill: resource.matchedSkills[0]?.skill ?? null,
      level: resource.matchedSkills[0]?.proficiency ?? null,
      chapter: resource.chapter ?? null,
    }));
  },

  async get_statistics(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createDashboardCaller(createScopedCallerContext(ctx));
    return caller.getStatisticsDetail();
  },

  async get_chargeability(params: { resourceId: string; month?: string }, ctx: ToolContext) {
    const now = new Date();
    const month = params.month ?? `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
    const resource = await resolveResourceIdentifier(ctx, params.resourceId);
    if ("error" in resource) {
      return resource;
    }

    const caller = createResourceCaller(createScopedCallerContext(ctx));
    return caller.getChargeabilitySummary({
      resourceId: resource.id,
      month,
    });
  },

  async get_chargeability_report(params: {
    startMonth: string;
    endMonth: string;
    orgUnitId?: string;
    managementLevelGroupId?: string;
    countryId?: string;
    includeProposed?: boolean;
    resourceQuery?: string;
    resourceLimit?: number;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.VIEW_COSTS);
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const caller = createChargeabilityReportCaller(createScopedCallerContext(ctx));
    return caller.getDetail({
      startMonth: params.startMonth,
      endMonth: params.endMonth,
      ...(params.orgUnitId ? { orgUnitId: params.orgUnitId } : {}),
      ...(params.managementLevelGroupId ? { managementLevelGroupId: params.managementLevelGroupId } : {}),
      ...(params.countryId ? { countryId: params.countryId } : {}),
      includeProposed: params.includeProposed ?? false,
      ...(params.resourceQuery ? { resourceQuery: params.resourceQuery } : {}),
      ...(params.resourceLimit !== undefined ? { resourceLimit: params.resourceLimit } : {}),
    });
  },

  async get_resource_computation_graph(params: {
    resourceId: string;
    month: string;
    domain?: string;
    includeLinks?: boolean;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.VIEW_COSTS);
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const resource = await resolveResourceIdentifier(ctx, params.resourceId);
    if ("error" in resource) {
      return resource;
    }

    const caller = createComputationGraphCaller(createScopedCallerContext(ctx));
    return caller.getResourceDataDetail({
      resourceId: resource.id,
      month: params.month,
      ...(params.domain ? { domain: params.domain } : {}),
      ...(params.includeLinks !== undefined ? { includeLinks: params.includeLinks } : {}),
    });
  },

  async get_project_computation_graph(params: {
    projectId: string;
    domain?: string;
    includeLinks?: boolean;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.VIEW_COSTS);
    assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS);

    const project = await resolveProjectIdentifier(ctx, params.projectId);
    if ("error" in project) {
      return project;
    }

    const caller = createComputationGraphCaller(createScopedCallerContext(ctx));
    return caller.getProjectDataDetail({
      projectId: project.id,
      ...(params.domain ? { domain: params.domain } : {}),
      ...(params.includeLinks !== undefined ? { includeLinks: params.includeLinks } : {}),
    });
  },

  async search_estimates(params: {
    projectCode?: string; query?: string; status?: string; limit?: number;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    let projectId: string | undefined;
    if (params.projectCode) {
      const project = await resolveProjectIdentifier(ctx, params.projectCode);
      if ("error" in project) {
        return project;
      }
      projectId = project.id;
    }

    return caller.list({
      ...(params.query ? { query: params.query } : {}),
      ...(params.status ? { status: params.status as EstimateStatus } : {}),
      ...(projectId ? { projectId } : {}),
    });
  },

  async list_clients(params: { query?: string; limit?: number }, ctx: ToolContext) {
    const limit = Math.min(params.limit ?? 20, 50);
    const caller = createClientCaller(createScopedCallerContext(ctx));
    const clients = await caller.list({
      isActive: true,
      ...(params.query ? { search: params.query } : {}),
    });
    return clients.slice(0, limit).map((c) => ({
      id: c.id,
      name: c.name,
      code: c.code,
      projectCount: c._count.projects,
    }));
  },

  async list_org_units(params: { level?: number }, ctx: ToolContext) {
    const caller = createOrgUnitCaller(createScopedCallerContext(ctx));
    const units = await caller.list({
      isActive: true,
      ...(params.level !== undefined ? { level: params.level } : {}),
    });
    const details = await Promise.all(units.map((unit) => caller.getById({ id: unit.id })));

    return details.map((u) => ({
      id: u.id,
      name: u.name,
      shortName: u.shortName,
      level: u.level,
      parent: u.parent?.name ?? null,
      resourceCount: u._count.resources,
    }));
  },

  // ── NAVIGATION TOOLS ──

  async navigate_to_page(params: {
    page: string;
    eids?: string; chapters?: string; projectIds?: string; clientIds?: string;
    countryCodes?: string; startDate?: string; days?: number;
  }, _ctx: ToolContext) {
    const pageMap: Record<string, string> = {
      timeline: "/timeline",
      dashboard: "/dashboard",
      resources: "/resources",
      projects: "/projects",
      allocations: "/allocations",
      staffing: "/staffing",
      estimates: "/estimates",
      vacations: "/vacations",
      "my-vacations": "/vacations/my",
      roles: "/roles",
      "skills-analytics": "/analytics/skills",
      chargeability: "/reports/chargeability",
      "computation-graph": "/analytics/computation-graph",
    };
    const path = pageMap[params.page];
    if (!path) return { error: `Unknown page: ${params.page}. Available: ${Object.keys(pageMap).join(", ")}` };

    // Build query params for pages that support them
    const queryParts: string[] = [];
    if (params.eids) queryParts.push(`eids=${encodeURIComponent(params.eids)}`);
    if (params.chapters) queryParts.push(`chapters=${encodeURIComponent(params.chapters)}`);
    if (params.projectIds) queryParts.push(`projectIds=${encodeURIComponent(params.projectIds)}`);
    if (params.clientIds) queryParts.push(`clientIds=${encodeURIComponent(params.clientIds)}`);
    if (params.countryCodes) queryParts.push(`countryCodes=${encodeURIComponent(params.countryCodes)}`);
    if (params.startDate) queryParts.push(`startDate=${encodeURIComponent(params.startDate)}`);
    if (params.days) queryParts.push(`days=${params.days}`);

    const url = queryParts.length > 0 ? `${path}?${queryParts.join("&")}` : path;

    return {
      __action: "navigate",
      url,
      description: `Navigiere zu ${path}`,
    };
  },

  // ── WRITE TOOLS ──

  async create_allocation(params: {
    resourceId: string; projectId: string;
    startDate: string; endDate: string;
    hoursPerDay: number; role?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, "manageAllocations" as PermissionKey);
    const [resource, project] = await Promise.all([
      resolveResourceIdentifier(ctx, params.resourceId),
      resolveProjectIdentifier(ctx, params.projectId),
    ]);
    if ("error" in resource) {
      return resource;
    }
    if ("error" in project) {
      return project;
    }

    const caller = createAllocationCaller(createScopedCallerContext(ctx));
    try {
      const result = await caller.ensureAssignment({
        resourceId: resource.id,
        projectId: project.id,
        startDate: new Date(`${params.startDate}T00:00:00.000Z`),
        endDate: new Date(`${params.endDate}T00:00:00.000Z`),
        hoursPerDay: params.hoursPerDay,
        ...(params.role ? { role: params.role } : {}),
      });

      return {
        __action: "invalidate",
        scope: ["allocation", "timeline"],
        success: true,
        message: `${result.action === "reactivated" ? "Reactivated" : "Created"} allocation: ${resource.displayName} → ${project.name} (${project.shortCode}), ${params.hoursPerDay}h/day, ${params.startDate} to ${params.endDate}`,
        allocationId: result.assignment.id,
        status: result.assignment.status,
      };
    } catch (error) {
      if (error instanceof TRPCError && error.code === "CONFLICT") {
        return { error: "Allocation already exists for this resource/project/dates. No new allocation created." };
      }
      throw error;
    }
  },

  async cancel_allocation(params: {
    allocationId?: string;
    resourceName?: string; projectCode?: string;
    startDate?: string; endDate?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, "manageAllocations" as PermissionKey);

    const caller = createAllocationCaller(createScopedCallerContext(ctx));
    let resourceId: string | undefined;
    let projectId: string | undefined;
    if (!params.allocationId && params.resourceName && params.projectCode) {
      const [resource, project] = await Promise.all([
        resolveResourceIdentifier(ctx, params.resourceName),
        resolveProjectIdentifier(ctx, params.projectCode),
      ]);
      if ("error" in resource) {
        return resource;
      }
      if ("error" in project) {
        return project;
      }
      resourceId = resource.id;
      projectId = project.id;
    }

    let assignment;
    try {
      assignment = await caller.resolveAssignment({
        ...(params.allocationId ? { assignmentId: params.allocationId } : {}),
        ...(resourceId ? { resourceId } : {}),
        ...(projectId ? { projectId } : {}),
        ...(params.startDate ? { startDate: new Date(`${params.startDate}T00:00:00.000Z`) } : {}),
        ...(params.endDate ? { endDate: new Date(`${params.endDate}T00:00:00.000Z`) } : {}),
        selectionMode: "WINDOW",
        excludeCancelled: true,
      });
    } catch {
      return { error: "Allocation not found with the given criteria." };
    }

    await caller.updateAssignment({
      id: assignment.id,
      data: UpdateAssignmentSchema.parse({ status: AllocationStatus.CANCELLED }),
    });

    return {
      __action: "invalidate",
      scope: ["allocation", "timeline"],
      success: true,
      message: `Cancelled allocation: ${assignment.resource.displayName} → ${assignment.project.name} (${assignment.project.shortCode}), ${fmtDate(assignment.startDate)} to ${fmtDate(assignment.endDate)}`,
    };
  },

  async update_allocation_status(params: {
    allocationId?: string;
    resourceName?: string; projectCode?: string;
    startDate?: string;
    newStatus: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, "manageAllocations" as PermissionKey);

    const validStatuses = ["PROPOSED", "CONFIRMED", "ACTIVE", "COMPLETED", "CANCELLED"];
    if (!validStatuses.includes(params.newStatus)) {
      return { error: `Invalid status: ${params.newStatus}. Valid: ${validStatuses.join(", ")}` };
    }

    const caller = createAllocationCaller(createScopedCallerContext(ctx));
    let resourceId: string | undefined;
    let projectId: string | undefined;
    if (!params.allocationId && params.resourceName && params.projectCode) {
      const [resource, project] = await Promise.all([
        resolveResourceIdentifier(ctx, params.resourceName),
        resolveProjectIdentifier(ctx, params.projectCode),
      ]);
      if ("error" in resource) {
        return resource;
      }
      if ("error" in project) {
        return project;
      }
      resourceId = resource.id;
      projectId = project.id;
    }

    let assignment;
    try {
      assignment = await caller.resolveAssignment({
        ...(params.allocationId ? { assignmentId: params.allocationId } : {}),
        ...(resourceId ? { resourceId } : {}),
        ...(projectId ? { projectId } : {}),
        ...(params.startDate ? { startDate: new Date(`${params.startDate}T00:00:00.000Z`) } : {}),
        selectionMode: "EXACT_START",
      });
    } catch {
      return { error: "Allocation not found with the given criteria." };
    }

    const oldStatus = assignment.status;
    await caller.updateAssignment({
      id: assignment.id,
      data: UpdateAssignmentSchema.parse({
        status: params.newStatus as AllocationStatus,
      }),
    });

    return {
      __action: "invalidate",
      scope: ["allocation", "timeline"],
      success: true,
      message: `Updated allocation status: ${assignment.resource.displayName} → ${assignment.project.name} (${assignment.project.shortCode}), ${fmtDate(assignment.startDate)} to ${fmtDate(assignment.endDate)}: ${oldStatus} → ${params.newStatus}`,
    };
  },

  async update_resource(params: {
    id: string; displayName?: string; fte?: number;
    lcrCents?: number; chapter?: string; chargeabilityTarget?: number;
  }, ctx: ToolContext) {
    assertPermission(ctx, "manageResources" as PermissionKey);
    const resource = await resolveResourceIdentifier(ctx, params.id);
    if ("error" in resource) return resource;

    const caller = createResourceCaller(createScopedCallerContext(ctx));
    const data = UpdateResourceSchema.parse({
      ...(params.displayName !== undefined ? { displayName: params.displayName } : {}),
      ...(params.fte !== undefined ? { fte: params.fte } : {}),
      ...(params.lcrCents !== undefined ? { lcrCents: params.lcrCents } : {}),
      ...(params.chapter !== undefined ? { chapter: params.chapter } : {}),
      ...(params.chargeabilityTarget !== undefined ? { chargeabilityTarget: params.chargeabilityTarget } : {}),
    });

    const updatedFields = Object.keys(data);
    if (updatedFields.length === 0) return { error: "No fields to update" };

    const updated = await caller.update({ id: resource.id, data });
    return {
      __action: "invalidate",
      scope: ["resource"],
      success: true,
      message: `Updated resource ${updated.displayName} (${updated.eid})`,
      updatedFields,
    };
  },

  async update_project(params: {
    id: string; name?: string; budgetCents?: number;
    winProbability?: number; status?: string;
    responsiblePerson?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, "manageProjects" as PermissionKey);
    const project = await resolveProjectIdentifier(ctx, params.id);
    if ("error" in project) return project;

    const data: Record<string, unknown> = {};
    if (params.name !== undefined) data.name = params.name;
    if (params.budgetCents !== undefined) data.budgetCents = params.budgetCents;
    if (params.winProbability !== undefined) data.winProbability = params.winProbability;
    if (params.status !== undefined) data.status = params.status;

    // Validate responsible person against existing resources
    if (params.responsiblePerson !== undefined) {
      const result = await resolveResponsiblePerson(params.responsiblePerson, ctx);
      if ("error" in result) return { error: result.error };
      data.responsiblePerson = result.displayName;
    }

    const parsedData = UpdateProjectSchema.parse(data);
    const updatedFields = Object.keys(parsedData);
    if (updatedFields.length === 0) return { error: "No fields to update" };

    const caller = createProjectCaller(createScopedCallerContext(ctx));
    const updated = await caller.update({ id: project.id, data: parsedData });
    return {
      __action: "invalidate",
      scope: ["project"],
      success: true,
      message: `Updated project ${updated.name} (${updated.shortCode})`,
      updatedFields,
    };
  },

  async create_project(params: {
    shortCode: string; name: string; orderType: string;
    allocationType?: string; budgetCents: number;
    startDate: string; endDate: string;
    winProbability?: number; status?: string;
    responsiblePerson?: string; color?: string;
    blueprintName?: string; clientName?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, "manageProjects" as PermissionKey);
    if (!params.responsiblePerson?.trim()) {
      return { error: "responsiblePerson is required to create a project." };
    }

    // Validate responsible person against existing resources
    const responsible = await resolveResponsiblePerson(params.responsiblePerson, ctx);
    if ("error" in responsible) return { error: responsible.error };

    const blueprintCaller = createBlueprintCaller(createScopedCallerContext(ctx));
    const clientCaller = createClientCaller(createScopedCallerContext(ctx));

    let blueprintId: string | undefined;
    if (params.blueprintName) {
      const blueprint = await resolveEntityOrAssistantError(
        () => blueprintCaller.resolveByIdentifier({ identifier: params.blueprintName! }),
        `Blueprint not found: "${params.blueprintName}"`,
      );
      if ("error" in blueprint) {
        return blueprint;
      }
      blueprintId = blueprint.id;
    }

    let clientId: string | undefined;
    if (params.clientName) {
      const client = await resolveEntityOrAssistantError(
        () => clientCaller.resolveByIdentifier({ identifier: params.clientName! }),
        `Client not found: "${params.clientName}"`,
      );
      if ("error" in client) {
        return client;
      }
      clientId = client.id;
    }

    const input = CreateProjectSchema.parse({
      shortCode: params.shortCode,
      name: params.name,
      orderType: params.orderType,
      allocationType: params.allocationType ?? "INT",
      budgetCents: params.budgetCents,
      startDate: params.startDate,
      endDate: params.endDate,
      winProbability: params.winProbability ?? 100,
      status: params.status ?? "DRAFT",
      responsiblePerson: responsible.displayName,
      ...(params.color ? { color: params.color } : {}),
      ...(blueprintId ? { blueprintId } : {}),
      ...(clientId ? { clientId } : {}),
      staffingReqs: [],
      dynamicFields: {},
    });

    const caller = createProjectCaller(createScopedCallerContext(ctx));
    const project = await caller.create(input);

    return {
      __action: "invalidate",
      scope: ["project"],
      success: true,
      message: `Created project: ${project.name} (${project.shortCode}), budget ${fmtEur(params.budgetCents)}, ${params.startDate} to ${params.endDate}, status: ${project.status}`,
      projectId: project.id,
      shortCode: project.shortCode,
    };
  },

  // ── RESOURCE MANAGEMENT ──

  async create_resource(params: {
    eid: string; displayName: string; email?: string;
    fte?: number; lcrCents: number; ucrCents?: number;
    chapter?: string; chargeabilityTarget?: number;
    roleName?: string; countryCode?: string; orgUnitName?: string;
    postalCode?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, "manageResources" as PermissionKey);
    if (!params.email?.trim()) {
      return { error: "email is required to create a resource." };
    }

    const roleCaller = createRoleCaller(createScopedCallerContext(ctx));
    const countryCaller = createCountryCaller(createScopedCallerContext(ctx));
    const orgUnitCaller = createOrgUnitCaller(createScopedCallerContext(ctx));

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const data: Record<string, any> = {
      eid: params.eid,
      displayName: params.displayName,
      email: params.email,
      lcrCents: params.lcrCents,
      ucrCents: params.ucrCents ?? Math.round(params.lcrCents * 0.7),
      chargeabilityTarget: params.chargeabilityTarget ?? 80,
      currency: "EUR",
      availability: {
        monday: 8,
        tuesday: 8,
        wednesday: 8,
        thursday: 8,
        friday: 8,
      },
      skills: [],
      dynamicFields: {},
      ...(params.fte !== undefined ? { fte: params.fte } : {}),
    };
    if (params.chapter) data.chapter = params.chapter;
    if (params.postalCode) data.postalCode = params.postalCode;

    if (params.roleName) {
      const role = await resolveEntityOrAssistantError(
        () => roleCaller.resolveByIdentifier({ identifier: params.roleName! }),
        `Role not found: "${params.roleName}"`,
      );
      if ("error" in role) {
        return role;
      }
      data.roleId = role.id;
    }
    if (params.countryCode) {
      const country = await resolveEntityOrAssistantError(
        () => countryCaller.resolveByIdentifier({ identifier: params.countryCode! }),
        `Country not found: "${params.countryCode}"`,
      );
      if ("error" in country) {
        return country;
      }
      data.countryId = country.id;
    }
    if (params.orgUnitName) {
      const orgUnit = await resolveEntityOrAssistantError(
        () => orgUnitCaller.resolveByIdentifier({ identifier: params.orgUnitName! }),
        `Org unit not found: "${params.orgUnitName}"`,
      );
      if ("error" in orgUnit) {
        return orgUnit;
      }
      data.orgUnitId = orgUnit.id;
    }

    const input = CreateResourceSchema.parse(data);
    const caller = createResourceCaller(createScopedCallerContext(ctx));
    const resource = await caller.create(input);

    return {
      __action: "invalidate",
      scope: ["resource"],
      success: true,
      message: `Created resource: ${resource.displayName} (${resource.eid})`,
      resourceId: resource.id,
    };
  },

  async deactivate_resource(params: { identifier: string }, ctx: ToolContext) {
    assertPermission(ctx, "manageResources" as PermissionKey);
    const resource = await resolveResourceIdentifier(ctx, params.identifier);
    if ("error" in resource) return resource;

    const caller = createResourceCaller(createScopedCallerContext(ctx));
    await caller.deactivate({ id: resource.id });
    if (!resource) return { error: `Resource not found: ${params.identifier}` };
    return {
      __action: "invalidate",
      scope: ["resource"],
      success: true,
      message: `Deactivated resource: ${resource.displayName} (${resource.eid})`,
    };
  },

  // ── VACATION MANAGEMENT ──

  async create_vacation(params: {
    resourceId: string; type: string;
    startDate: string; endDate: string;
    isHalfDay?: boolean; halfDayPart?: string; note?: string;
  }, ctx: ToolContext) {
    const resource = await resolveResourceIdentifier(ctx, params.resourceId);
    if ("error" in resource) return resource;

    const caller = createVacationCaller(createScopedCallerContext(ctx));
    const type = parseAssistantVacationRequestType(params.type);
    const vacation = await caller.create({
      resourceId: resource.id,
      type,
      startDate: new Date(params.startDate),
      endDate: new Date(params.endDate),
      ...(params.isHalfDay !== undefined ? { isHalfDay: params.isHalfDay } : {}),
      ...(params.halfDayPart !== undefined ? { halfDayPart: params.halfDayPart as "MORNING" | "AFTERNOON" } : {}),
      ...(params.note !== undefined ? { note: params.note } : {}),
    });
    const effectiveDays = "effectiveDays" in vacation && typeof vacation.effectiveDays === "number"
      ? vacation.effectiveDays
      : null;

    return {
      __action: "invalidate",
      scope: ["vacation"],
      success: true,
      message: `Created ${type} for ${resource.displayName}: ${params.startDate} to ${params.endDate} (status: ${vacation.status}${effectiveDays !== null ? `, deducted ${effectiveDays} day(s)` : ""})`,
      vacationId: vacation.id,
      vacation,
    };
  },

  async approve_vacation(params: { vacationId: string }, ctx: ToolContext) {
    const caller = createVacationCaller(createScopedCallerContext(ctx));
    const existing = await caller.getById({ id: params.vacationId });
    const approved = await caller.approve({ id: params.vacationId });
    return {
      __action: "invalidate",
      scope: ["vacation"],
      success: true,
      warnings: approved.warnings,
      vacation: approved,
      message: `Approved vacation for ${existing.resource?.displayName ?? params.vacationId}`,
    };
  },

  async reject_vacation(params: { vacationId: string; reason?: string }, ctx: ToolContext) {
    const caller = createVacationCaller(createScopedCallerContext(ctx));
    const existing = await caller.getById({ id: params.vacationId });
    const rejected = await caller.reject({
      id: params.vacationId,
      ...(params.reason !== undefined ? { rejectionReason: params.reason } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["vacation"],
      success: true,
      vacation: rejected,
      message: `Rejected vacation for ${existing.resource?.displayName ?? params.vacationId}${params.reason ? `: ${params.reason}` : ""}`,
    };
  },

  async cancel_vacation(params: { vacationId: string }, ctx: ToolContext) {
    const caller = createVacationCaller(createScopedCallerContext(ctx));
    const existing = await caller.getById({ id: params.vacationId });
    const cancelled = await caller.cancel({ id: params.vacationId });
    return {
      __action: "invalidate",
      scope: ["vacation"],
      success: true,
      vacation: cancelled,
      message: `Cancelled vacation for ${existing.resource?.displayName ?? params.vacationId}`,
    };
  },

  async get_pending_vacation_approvals(params: { limit?: number }, ctx: ToolContext) {
    const limit = Math.min(params.limit ?? 20, 50);
    const caller = createVacationCaller(createScopedCallerContext(ctx));
    const vacations = await caller.getPendingApprovals();
    return vacations.map((v) => ({
      id: v.id,
      resource: v.resource.displayName,
      eid: v.resource.eid,
      chapter: v.resource.chapter,
      type: v.type,
      start: fmtDate(v.startDate),
      end: fmtDate(v.endDate),
      isHalfDay: v.isHalfDay,
    })).slice(0, limit);
  },

  async get_team_vacation_overlap(params: {
    resourceId: string; startDate: string; endDate: string;
  }, ctx: ToolContext) {
    const resource = await resolveResourceIdentifier(ctx, params.resourceId);
    if ("error" in resource) {
      return resource;
    }

    const caller = createVacationCaller(createScopedCallerContext(ctx));
    return caller.getTeamOverlapDetail({
      resourceId: resource.id,
      startDate: new Date(params.startDate),
      endDate: new Date(params.endDate),
    });
  },

  // ── ENTITLEMENT ──

  async get_entitlement_summary(params: { year?: number; resourceName?: string }, ctx: ToolContext) {
    const year = params.year ?? new Date().getFullYear();
    const caller = createEntitlementCaller(createScopedCallerContext(ctx));
    return caller.getYearSummaryDetail({
      year,
      ...(params.resourceName ? { resourceName: params.resourceName } : {}),
    });
  },

  async set_entitlement(params: {
    resourceId: string; year: number; entitledDays: number; carryoverDays?: number;
  }, ctx: ToolContext) {
    if (params.carryoverDays !== undefined) {
      return {
        error: "Manual carryoverDays is not supported here. Carryover is computed automatically from prior-year balances.",
      };
    }

    const resource = await resolveResourceIdentifier(ctx, params.resourceId);
    if ("error" in resource) return resource;

    const caller = createEntitlementCaller(createScopedCallerContext(ctx));
    const entitlement = await caller.set({
      resourceId: resource.id,
      year: params.year,
      entitledDays: params.entitledDays,
    });

    return {
      __action: "invalidate",
      scope: ["vacation"],
      success: true,
      entitlement,
      message: `Set entitlement for ${resource.displayName} (${params.year}): ${params.entitledDays} days`,
    };
  },

  // ── DEMAND / STAFFING ──

  async list_demands(params: { projectId?: string; status?: string; limit?: number }, ctx: ToolContext) {
    const limit = Math.min(params.limit ?? 30, 50);
    const caller = createAllocationCaller(createScopedCallerContext(ctx));
    const resolvedProject = params.projectId
      ? await resolveProjectIdentifier(ctx, params.projectId)
      : null;
    if (resolvedProject && "error" in resolvedProject) {
      return resolvedProject;
    }
    const demands = await caller.listDemands({
      ...(resolvedProject ? { projectId: resolvedProject.id } : {}),
      ...(params.status ? { status: params.status as AllocationStatus } : {}),
    });

    return demands.map((d) => ({
      id: d.id,
      project: d.project.name,
      projectCode: d.project.shortCode,
      role: d.roleEntity?.name ?? d.role ?? "Unspecified",
      status: d.status,
      headcount: d.headcount,
      filled: d.assignments.length,
      remaining: d.headcount - d.assignments.length,
      hoursPerDay: d.hoursPerDay,
      start: fmtDate(d.startDate),
      end: fmtDate(d.endDate),
    })).slice(0, limit);
  },

  async create_demand(params: {
    projectId: string; roleName: string; headcount?: number;
    hoursPerDay: number; startDate: string; endDate: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, "manageAllocations" as PermissionKey);
    const roleCaller = createRoleCaller(createScopedCallerContext(ctx));
    const [project, role] = await Promise.all([
      resolveProjectIdentifier(ctx, params.projectId),
      resolveEntityOrAssistantError(
        () => roleCaller.resolveByIdentifier({ identifier: params.roleName }),
        `Role not found: ${params.roleName}`,
      ),
    ]);
    if ("error" in project) {
      return project;
    }
    if ("error" in role) {
      return role;
    }

    const caller = createAllocationCaller(createScopedCallerContext(ctx));
    const demand = await caller.createDemand({
      projectId: project.id,
      roleId: role.id,
      role: role.name,
      headcount: params.headcount ?? 1,
      hoursPerDay: params.hoursPerDay,
      startDate: parseIsoDate(params.startDate, "startDate"),
      endDate: parseIsoDate(params.endDate, "endDate"),
    });

    return {
      __action: "invalidate",
      scope: ["allocation"],
      success: true,
      message: `Created demand: ${role.name} × ${params.headcount ?? 1} for ${project.name} (${project.shortCode}), ${params.hoursPerDay}h/day, ${params.startDate} to ${params.endDate}`,
      demandId: demand.id,
    };
  },

  async fill_demand(params: { demandId: string; resourceId: string }, ctx: ToolContext) {
    assertPermission(ctx, "manageAllocations" as PermissionKey);
    const allocationCaller = createAllocationCaller(createScopedCallerContext(ctx));
    const resource = await resolveResourceIdentifier(ctx, params.resourceId);
    if ("error" in resource) return resource;

    const result = await allocationCaller.assignResourceToDemand({
      demandRequirementId: params.demandId,
      resourceId: resource.id,
    });
    const roleName = result.demandRequirement.roleEntity?.name ?? result.demandRequirement.role ?? null;

    return {
      __action: "invalidate",
      scope: ["allocation", "timeline"],
      success: true,
      message: `Assigned ${resource.displayName} to ${roleName ?? "demand"} on ${result.demandRequirement.project.name} (${result.demandRequirement.project.shortCode})`,
      assignmentId: result.assignment.id,
    };
  },

  async check_resource_availability(params: {
    resourceId: string; startDate: string; endDate: string;
  }, ctx: ToolContext) {
    const resource = await resolveResourceIdentifier(ctx, params.resourceId);
    if ("error" in resource) {
      return resource;
    }

    const caller = createAllocationCaller(createScopedCallerContext(ctx));
    return caller.getResourceAvailabilitySummary({
      resourceId: resource.id,
      startDate: new Date(`${params.startDate}T00:00:00.000Z`),
      endDate: new Date(`${params.endDate}T00:00:00.000Z`),
    });
  },

  async get_staffing_suggestions(params: {
    projectId: string; roleName?: string;
    startDate?: string; endDate?: string; limit?: number;
  }, ctx: ToolContext) {
    const project = await resolveProjectIdentifier(ctx, params.projectId);
    if ("error" in project) {
      return project;
    }

    const caller = createStaffingCaller(createScopedCallerContext(ctx));
    return caller.getProjectStaffingSuggestions({
      projectId: project.id,
      ...(params.roleName ? { roleName: params.roleName } : {}),
      ...(params.startDate ? { startDate: new Date(params.startDate) } : {}),
      ...(params.endDate ? { endDate: new Date(params.endDate) } : {}),
      ...(params.limit ? { limit: params.limit } : {}),
    });
  },

  async find_capacity(params: {
    startDate: string; endDate: string;
    minHoursPerDay?: number; roleName?: string;
    chapter?: string; limit?: number;
  }, ctx: ToolContext) {
    const caller = createStaffingCaller(createScopedCallerContext(ctx));
    return caller.searchCapacity({
      startDate: new Date(params.startDate),
      endDate: new Date(params.endDate),
      minHoursPerDay: params.minHoursPerDay ?? 4,
      ...(params.roleName ? { roleName: params.roleName } : {}),
      ...(params.chapter ? { chapter: params.chapter } : {}),
      ...(params.limit ? { limit: params.limit } : {}),
    });
  },

  // ── BLUEPRINT ──

  async list_blueprints(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createBlueprintCaller(createScopedCallerContext(ctx));
    const blueprints = await caller.listSummaries();
    return blueprints.map((b) => ({
      id: b.id,
      name: b.name,
      projectCount: b._count.projects,
    }));
  },

  async get_blueprint(params: { identifier: string }, ctx: ToolContext) {
    const caller = createBlueprintCaller(createScopedCallerContext(ctx));
    const bp = await caller.getByIdentifier({ identifier: params.identifier });
    return {
      id: bp.id,
      name: bp.name,
      fieldDefs: bp.fieldDefs,
      rolePresets: bp.rolePresets,
    };
  },

  // ── RATE CARDS ──

  async list_rate_cards(params: { query?: string; limit?: number }, ctx: ToolContext) {
    const caller = createRateCardCaller(createScopedCallerContext(ctx));
    const cards = await caller.list({
      isActive: true,
      ...(params.query ? { search: params.query } : {}),
    });
    return cards.map((c) => ({
      id: c.id,
      name: c.name,
      effectiveFrom: fmtDate(c.effectiveFrom),
      effectiveTo: fmtDate(c.effectiveTo),
      lineCount: c._count.lines,
    })).slice(0, Math.min(params.limit ?? 20, 50));
  },

  async resolve_rate(params: { resourceId?: string; roleName?: string; date?: string }, ctx: ToolContext) {
    const caller = createRateCardCaller(createScopedCallerContext(ctx));
    if (params.resourceId) {
      const resource = await resolveResourceIdentifier(ctx, params.resourceId);
      if ("error" in resource) {
        return resource;
      }
      return caller.resolveBestRate({
        resourceId: resource.id,
        ...(params.date ? { date: new Date(params.date) } : {}),
      });
    }

    return caller.resolveBestRate({
      ...(params.roleName ? { roleName: params.roleName } : {}),
      ...(params.date ? { date: new Date(params.date) } : {}),
    });
  },

  // ── ESTIMATES ──

  async get_estimate_detail(params: { estimateId: string }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.VIEW_COSTS);
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    return caller.getById({ id: params.estimateId });
  },

  async list_estimate_versions(params: { estimateId: string }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    return caller.listVersions({ estimateId: params.estimateId });
  },

  async get_estimate_version_snapshot(params: {
    estimateId: string;
    versionId?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.VIEW_COSTS);
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    return caller.getVersionSnapshot({
      estimateId: params.estimateId,
      ...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
    });
  },

  async create_estimate(params: {
    projectId?: string;
    projectCode?: string;
    name: string;
    opportunityId?: string;
    baseCurrency?: string;
    status?: EstimateStatus;
    versionLabel?: string;
    versionNotes?: string;
    assumptions?: CreateEstimateInput["assumptions"];
    scopeItems?: CreateEstimateInput["scopeItems"];
    demandLines?: CreateEstimateInput["demandLines"];
    resourceSnapshots?: CreateEstimateInput["resourceSnapshots"];
    metrics?: CreateEstimateInput["metrics"];
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    let projectId = params.projectId;
    if (!projectId && params.projectCode) {
      const project = await resolveProjectIdentifier(ctx, params.projectCode);
      if ("error" in project) {
        return project;
      }
      projectId = project.id;
    }

    const estimate = await caller.create({
      name: params.name,
      ...(projectId ? { projectId } : {}),
      ...(params.opportunityId !== undefined ? { opportunityId: params.opportunityId } : {}),
      ...(params.baseCurrency !== undefined ? { baseCurrency: params.baseCurrency } : {}),
      ...(params.status !== undefined ? { status: params.status } : {}),
      ...(params.versionLabel !== undefined ? { versionLabel: params.versionLabel } : {}),
      ...(params.versionNotes !== undefined ? { versionNotes: params.versionNotes } : {}),
      ...(params.assumptions !== undefined ? { assumptions: params.assumptions } : {}),
      ...(params.scopeItems !== undefined ? { scopeItems: params.scopeItems } : {}),
      ...(params.demandLines !== undefined ? { demandLines: params.demandLines } : {}),
      ...(params.resourceSnapshots !== undefined ? { resourceSnapshots: params.resourceSnapshots } : {}),
      ...(params.metrics !== undefined ? { metrics: params.metrics } : {}),
    });

    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      estimate,
      estimateId: estimate.id,
      message: `Created estimate "${estimate.name}".`,
    };
  },

  async clone_estimate(params: {
    sourceEstimateId: string;
    name?: string;
    projectId?: string;
    projectCode?: string;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    let projectId = params.projectId;
    if (!projectId && params.projectCode) {
      const project = await resolveProjectIdentifier(ctx, params.projectCode);
      if ("error" in project) {
        return project;
      }
      projectId = project.id;
    }

    const estimate = await caller.clone({
      sourceEstimateId: params.sourceEstimateId,
      ...(params.name !== undefined ? { name: params.name } : {}),
      ...(projectId ? { projectId } : {}),
    });

    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      estimate,
      estimateId: estimate.id,
      message: `Cloned estimate "${estimate.name}".`,
    };
  },

  async update_estimate_draft(params: {
    id: string;
    projectId?: string;
    projectCode?: string;
    name?: string;
    opportunityId?: string;
    baseCurrency?: string;
    status?: EstimateStatus;
    versionLabel?: string;
    versionNotes?: string;
    assumptions?: UpdateEstimateDraftInput["assumptions"];
    scopeItems?: UpdateEstimateDraftInput["scopeItems"];
    demandLines?: UpdateEstimateDraftInput["demandLines"];
    resourceSnapshots?: UpdateEstimateDraftInput["resourceSnapshots"];
    metrics?: UpdateEstimateDraftInput["metrics"];
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    let projectId = params.projectId;
    if (!projectId && params.projectCode) {
      const project = await resolveProjectIdentifier(ctx, params.projectCode);
      if ("error" in project) {
        return project;
      }
      projectId = project.id;
    }

    const estimate = await caller.updateDraft({
      id: params.id,
      ...(projectId ? { projectId } : {}),
      ...(params.name !== undefined ? { name: params.name } : {}),
      ...(params.opportunityId !== undefined ? { opportunityId: params.opportunityId } : {}),
      ...(params.baseCurrency !== undefined ? { baseCurrency: params.baseCurrency } : {}),
      ...(params.status !== undefined ? { status: params.status } : {}),
      ...(params.versionLabel !== undefined ? { versionLabel: params.versionLabel } : {}),
      ...(params.versionNotes !== undefined ? { versionNotes: params.versionNotes } : {}),
      ...(params.assumptions !== undefined ? { assumptions: params.assumptions } : {}),
      ...(params.scopeItems !== undefined ? { scopeItems: params.scopeItems } : {}),
      ...(params.demandLines !== undefined ? { demandLines: params.demandLines } : {}),
      ...(params.resourceSnapshots !== undefined ? { resourceSnapshots: params.resourceSnapshots } : {}),
      ...(params.metrics !== undefined ? { metrics: params.metrics } : {}),
    });

    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      estimate,
      estimateId: estimate.id,
      message: `Updated estimate draft "${estimate.name}".`,
    };
  },

  async submit_estimate_version(params: {
    estimateId: string;
    versionId?: string;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    const estimate = await caller.submitVersion({
      estimateId: params.estimateId,
      ...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      estimate,
      estimateId: estimate.id,
      message: `Submitted estimate version for "${estimate.name}".`,
    };
  },

  async approve_estimate_version(params: {
    estimateId: string;
    versionId?: string;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    const estimate = await caller.approveVersion({
      estimateId: params.estimateId,
      ...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      estimate,
      estimateId: estimate.id,
      message: `Approved estimate version for "${estimate.name}".`,
    };
  },

  async create_estimate_revision(params: {
    estimateId: string;
    sourceVersionId?: string;
    label?: string;
    notes?: string;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    const estimate = await caller.createRevision({
      estimateId: params.estimateId,
      ...(params.sourceVersionId !== undefined ? { sourceVersionId: params.sourceVersionId } : {}),
      ...(params.label !== undefined ? { label: params.label } : {}),
      ...(params.notes !== undefined ? { notes: params.notes } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      estimate,
      estimateId: estimate.id,
      message: `Created a new estimate revision for "${estimate.name}".`,
    };
  },

  async create_estimate_export(params: {
    estimateId: string;
    versionId?: string;
    format: EstimateExportFormat;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    const estimate = await caller.createExport({
      estimateId: params.estimateId,
      format: params.format,
      ...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      estimate,
      estimateId: estimate.id,
      message: `Created ${params.format} export for estimate "${estimate.name}".`,
    };
  },

  async create_estimate_planning_handoff(params: {
    estimateId: string;
    versionId?: string;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    const result = await caller.createPlanningHandoff({
      estimateId: params.estimateId,
      ...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["estimate", "allocation", "timeline"],
      success: true,
      ...result,
      message: `Created planning handoff for estimate ${params.estimateId}.`,
    };
  },

  async generate_estimate_weekly_phasing(params: {
    estimateId: string;
    startDate: string;
    endDate: string;
    pattern?: "even" | "front_loaded" | "back_loaded" | "custom";
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    const result = await caller.generateWeeklyPhasing({
      estimateId: params.estimateId,
      startDate: params.startDate,
      endDate: params.endDate,
      ...(params.pattern !== undefined ? { pattern: params.pattern } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      ...result,
      message: `Generated weekly phasing for estimate ${params.estimateId}.`,
    };
  },

  async get_estimate_weekly_phasing(params: {
    estimateId: string;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    return caller.getWeeklyPhasing({ estimateId: params.estimateId });
  },

  async get_estimate_commercial_terms(params: {
    estimateId: string;
    versionId?: string;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    return caller.getCommercialTerms({
      estimateId: params.estimateId,
      ...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
    });
  },

  async update_estimate_commercial_terms(params: {
    estimateId: string;
    versionId?: string;
    terms: Record<string, unknown>;
  }, ctx: ToolContext) {
    const caller = createEstimateCaller(createScopedCallerContext(ctx));
    const result = await caller.updateCommercialTerms({
      estimateId: params.estimateId,
      terms: params.terms,
      ...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["estimate"],
      success: true,
      ...result,
      message: `Updated commercial terms for estimate ${params.estimateId}.`,
    };
  },

  // ── ROLES ──

  async create_role(params: {
    name: string;
    description?: string;
    color?: string;
  }, ctx: ToolContext) {
    const caller = createRoleCaller(createScopedCallerContext(ctx));
    const role = await caller.create(CreateRoleSchema.parse(params));
    return { __action: "invalidate", scope: ["role"], success: true, message: `Created role: ${role.name}`, roleId: role.id, role };
  },

  async update_role(params: {
    id: string;
    name?: string;
    description?: string;
    color?: string;
    isActive?: boolean;
  }, ctx: ToolContext) {
    const caller = createRoleCaller(createScopedCallerContext(ctx));
    const data = UpdateRoleSchema.parse({
      ...(params.name !== undefined ? { name: params.name } : {}),
      ...(params.description !== undefined ? { description: params.description } : {}),
      ...(params.color !== undefined ? { color: params.color } : {}),
      ...(params.isActive !== undefined ? { isActive: params.isActive } : {}),
    });
    if (Object.keys(data).length === 0) return { error: "No fields to update" };
    const role = await caller.update({ id: params.id, data });
    return { __action: "invalidate", scope: ["role"], success: true, message: `Updated role: ${role.name}`, roleId: role.id, role };
  },

  async delete_role(params: { id: string }, ctx: ToolContext) {
    const caller = createRoleCaller(createScopedCallerContext(ctx));
    const role = await caller.getById({ id: params.id });
    await caller.delete({ id: params.id });
    return { __action: "invalidate", scope: ["role"], success: true, message: `Deleted role: ${role.name}` };
  },

  // ── CLIENTS ──

  async create_client(params: {
    name: string;
    code?: string;
    parentId?: string;
    sortOrder?: number;
    tags?: string[];
  }, ctx: ToolContext) {
    const caller = createClientCaller(createScopedCallerContext(ctx));
    const client = await caller.create(CreateClientSchema.parse(params));
    return { __action: "invalidate", scope: ["client"], success: true, message: `Created client: ${client.name}`, clientId: client.id, client };
  },

  async update_client(params: {
    id: string;
    name?: string;
    code?: string | null;
    sortOrder?: number;
    isActive?: boolean;
    parentId?: string | null;
    tags?: string[];
  }, ctx: ToolContext) {
    const caller = createClientCaller(createScopedCallerContext(ctx));
    const data = UpdateClientSchema.parse({
      ...(params.name !== undefined ? { name: params.name } : {}),
      ...(params.code !== undefined ? { code: params.code } : {}),
      ...(params.sortOrder !== undefined ? { sortOrder: params.sortOrder } : {}),
      ...(params.isActive !== undefined ? { isActive: params.isActive } : {}),
      ...(params.parentId !== undefined ? { parentId: params.parentId } : {}),
      ...(params.tags !== undefined ? { tags: params.tags } : {}),
    });
    if (Object.keys(data).length === 0) return { error: "No fields to update" };
    const client = await caller.update({ id: params.id, data });
    return { __action: "invalidate", scope: ["client"], success: true, message: `Updated client: ${client.name}`, clientId: client.id, client };
  },

  // ── ADMIN / CONFIG ──

  async list_countries(params: { includeInactive?: boolean; search?: string }, ctx: ToolContext) {
    const caller = createCountryCaller(createScopedCallerContext(ctx));
    const countries = await caller.list(
      params.includeInactive
        ? undefined
        : { isActive: true },
    );
    const normalizedSearch = params.search?.trim().toLowerCase();
    const filteredCountries = normalizedSearch
      ? countries.filter((country) =>
          country.code.toLowerCase().includes(normalizedSearch)
          || country.name.toLowerCase().includes(normalizedSearch))
      : countries;

    return {
      count: filteredCountries.length,
      countries: filteredCountries.map(formatCountry),
    };
  },

  async get_country(params: { identifier: string }, ctx: ToolContext) {
    const caller = createCountryCaller(createScopedCallerContext(ctx));
    const country = await caller.getByIdentifier({ identifier: params.identifier });
    return formatCountry(country);
  },

  async create_country(params: {
    code: string;
    name: string;
    dailyWorkingHours?: number;
    scheduleRules?: Prisma.JsonValue | null;
  }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createCountryCaller(createScopedCallerContext(ctx));
    const created = await caller.create(CreateCountrySchema.parse(params));

    return {
      __action: "invalidate",
      scope: ["country", "resource", "holidayCalendar", "vacation"],
      success: true,
      country: formatCountry(created),
      message: `Created country: ${created.name}`,
    };
  },

  async update_country(params: {
    id: string;
    data: {
      code?: string;
      name?: string;
      dailyWorkingHours?: number;
      scheduleRules?: Prisma.JsonValue | null;
      isActive?: boolean;
    };
  }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createCountryCaller(createScopedCallerContext(ctx));
    const input = {
      id: params.id,
      data: UpdateCountrySchema.parse(params.data),
    };
    const updated = await caller.update(input);

    return {
      __action: "invalidate",
      scope: ["country", "resource", "holidayCalendar", "vacation"],
      success: true,
      country: formatCountry(updated),
      message: `Updated country: ${updated.name}`,
    };
  },

  async create_metro_city(params: { countryId: string; name: string }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createCountryCaller(createScopedCallerContext(ctx));
    const created = await caller.createCity(CreateMetroCitySchema.parse(params));

    return {
      __action: "invalidate",
      scope: ["country", "resource", "holidayCalendar", "vacation"],
      success: true,
      metroCity: created,
      message: `Created metro city: ${created.name}`,
    };
  },

  async update_metro_city(params: { id: string; data: { name?: string } }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createCountryCaller(createScopedCallerContext(ctx));
    const input = {
      id: params.id,
      data: UpdateMetroCitySchema.parse(params.data),
    };
    const updated = await caller.updateCity(input);

    return {
      __action: "invalidate",
      scope: ["country", "resource", "holidayCalendar", "vacation"],
      success: true,
      metroCity: updated,
      message: `Updated metro city: ${updated.name}`,
    };
  },

  async delete_metro_city(params: { id: string }, ctx: ToolContext) {
    assertAdminRole(ctx);
    const caller = createCountryCaller(createScopedCallerContext(ctx));
    const deleted = await caller.deleteCity({ id: params.id });

    return {
      __action: "invalidate",
      scope: ["country", "resource", "holidayCalendar", "vacation"],
      success: true,
      message: `Deleted metro city: ${deleted.name ?? params.id}`,
    };
  },

  async list_management_levels(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createManagementLevelCaller(createScopedCallerContext(ctx));
    const groups = await caller.listGroups();
    return groups.map((g) => ({
      id: g.id,
      name: g.name,
      target: g.targetPercentage ? `${g.targetPercentage}%` : null,
      levels: g.levels.map((l: { id: string; name: string }) => ({ id: l.id, name: l.name })),
    }));
  },

  async list_utilization_categories(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUtilizationCategoryCaller(createScopedCallerContext(ctx));
    const categories = await caller.list();
    const categoriesWithCounts = await Promise.all(
      categories.map(async (category) => {
        const detailed = await caller.getById({ id: category.id });
        return {
          category,
          projectCount: detailed._count.projects,
        };
      }),
    );
    return categoriesWithCounts.map(({ category, projectCount }) => ({
      id: category.id,
      code: category.code,
      name: category.name,
      description: category.description,
      projectCount,
    }));
  },

  async list_calculation_rules(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createCalculationRuleCaller(createScopedCallerContext(ctx));
    const rules = await caller.list();
    return rules.map((rule) => ({
      id: rule.id,
      name: rule.name,
      description: rule.description,
      isActive: rule.isActive,
      triggerType: rule.triggerType,
      orderType: rule.orderType,
      costEffect: rule.costEffect,
      costReductionPercent: rule.costReductionPercent,
      chargeabilityEffect: rule.chargeabilityEffect,
      priority: rule.priority,
      project: rule.project
        ? {
            id: rule.project.id,
            name: rule.project.name,
            shortCode: rule.project.shortCode,
          }
        : null,
    }));
  },

  async list_effort_rules(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createEffortRuleCaller(createScopedCallerContext(ctx));
    const ruleSets = await caller.list();
    return ruleSets.flatMap((ruleSet) => ruleSet.rules.map((rule) => ({
      id: rule.id,
      description: rule.description,
      scopeType: rule.scopeType,
      discipline: rule.discipline,
      chapter: rule.chapter,
      unitMode: rule.unitMode,
      hoursPerUnit: rule.hoursPerUnit,
      sortOrder: rule.sortOrder,
      ruleSet: {
        name: ruleSet.name,
        isDefault: ruleSet.isDefault,
      },
    })));
  },

  async list_experience_multipliers(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createExperienceMultiplierCaller(createScopedCallerContext(ctx));
    const multiplierSets = await caller.list();
    return multiplierSets.flatMap((multiplierSet) => multiplierSet.rules.map((rule) => ({
      id: rule.id,
      description: rule.description,
      chapter: rule.chapter,
      location: rule.location,
      level: rule.level,
      costMultiplier: rule.costMultiplier,
      billMultiplier: rule.billMultiplier,
      shoringRatio: rule.shoringRatio,
      additionalEffortRatio: rule.additionalEffortRatio,
      sortOrder: rule.sortOrder,
      multiplierSet: {
        name: multiplierSet.name,
        isDefault: multiplierSet.isDefault,
      },
    })));
  },

  async list_users(params: { limit?: number }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const users = await caller.list();
    return users.slice(0, Math.min(params.limit ?? 50, 100));
  },

  async list_assignable_users(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    return caller.listAssignable();
  },

  async get_current_user(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    return caller.me();
  },

  async get_dashboard_layout(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    return caller.getDashboardLayout();
  },

  async save_dashboard_layout(params: { layout: unknown[] }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.saveDashboardLayout({ layout: params.layout });
    return {
      __action: "invalidate",
      scope: ["dashboard"],
      success: true,
      ...result,
      message: "Saved dashboard layout.",
    };
  },

  async get_favorite_project_ids(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    return caller.getFavoriteProjectIds();
  },

  async toggle_favorite_project(params: { projectId: string }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.toggleFavoriteProject({ projectId: params.projectId });
    return {
      __action: "invalidate",
      scope: ["project"],
      success: true,
      ...result,
      message: result.added ? "Added project to favorites." : "Removed project from favorites.",
    };
  },

  async get_column_preferences(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    return caller.getColumnPreferences();
  },

  async set_column_preferences(params: {
    view: "resources" | "projects" | "allocations" | "vacations" | "roles" | "users" | "blueprints";
    visible?: string[];
    sort?: { field: string; dir: "asc" | "desc" } | null;
    rowOrder?: string[] | null;
  }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.setColumnPreferences({
      view: params.view,
      ...(params.visible !== undefined ? { visible: params.visible } : {}),
      ...(params.sort !== undefined ? { sort: params.sort } : {}),
      ...(params.rowOrder !== undefined ? { rowOrder: params.rowOrder } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["user"],
      success: true,
      ...result,
      message: `Updated column preferences for ${params.view}.`,
    };
  },

  async generate_totp_secret(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.generateTotpSecret();
    return {
      __action: "invalidate",
      scope: ["user"],
      success: true,
      ...result,
      message: "Generated a new MFA TOTP secret.",
    };
  },

  async verify_and_enable_totp(params: { token: string }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.verifyAndEnableTotp({ token: params.token });
    return {
      __action: "invalidate",
      scope: ["user"],
      success: true,
      ...result,
      message: "Enabled MFA TOTP.",
    };
  },

  async get_mfa_status(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    return caller.getMfaStatus();
  },

  async get_active_user_count(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    return caller.activeCount();
  },

  async create_user(params: {
    email: string;
    name: string;
    systemRole?: SystemRole;
    password: string;
  }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const user = await caller.create({
      email: params.email,
      name: params.name,
      password: params.password,
      ...(params.systemRole !== undefined ? { systemRole: params.systemRole } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["user", "resource"],
      success: true,
      user,
      userId: user.id,
      message: `Created user ${user.name}.`,
    };
  },

  async set_user_password(params: { userId: string; password: string }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.setPassword(params);
    return {
      __action: "invalidate",
      scope: ["user"],
      ...result,
      message: `Reset password for user ${params.userId}.`,
    };
  },

  async update_user_role(params: { id: string; systemRole: SystemRole }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const user = await caller.updateRole(params);
    return {
      __action: "invalidate",
      scope: ["user"],
      success: true,
      user,
      userId: user.id,
      message: `Updated role for ${user.name} to ${user.systemRole}.`,
    };
  },

  async update_user_name(params: { id: string; name: string }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const user = await caller.updateName(params);
    return {
      __action: "invalidate",
      scope: ["user"],
      success: true,
      user,
      userId: user.id,
      message: `Updated user name to ${user.name}.`,
    };
  },

  async link_user_resource(params: { userId: string; resourceId?: string | null }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.linkResource({
      userId: params.userId,
      resourceId: params.resourceId ?? null,
    });
    return {
      __action: "invalidate",
      scope: ["user", "resource"],
      ...result,
      message: params.resourceId ? "Linked user to resource." : "Unlinked user resource.",
    };
  },

  async auto_link_users_by_email(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.autoLinkAllByEmail();
    return {
      __action: "invalidate",
      scope: ["user", "resource"],
      success: true,
      ...result,
      message: `Auto-linked ${result.linked} user(s) by email.`,
    };
  },

  async set_user_permissions(params: {
    userId: string;
    overrides?: {
      granted?: string[];
      denied?: string[];
      chapterIds?: string[];
    } | null;
  }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const user = await caller.setPermissions({
      userId: params.userId,
      overrides: params.overrides ?? null,
    });
    return {
      __action: "invalidate",
      scope: ["user"],
      success: true,
      user,
      userId: user.id,
      message: params.overrides ? "Updated user permission overrides." : "Cleared user permission overrides.",
    };
  },

  async reset_user_permissions(params: { userId: string }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const user = await caller.resetPermissions(params);
    return {
      __action: "invalidate",
      scope: ["user"],
      success: true,
      user,
      userId: user.id,
      message: "Reset user permissions to role defaults.",
    };
  },

  async get_effective_user_permissions(params: { userId: string }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    return caller.getEffectivePermissions(params);
  },

  async disable_user_totp(params: { userId: string }, ctx: ToolContext) {
    const caller = createUserCaller(createScopedCallerContext(ctx));
    const result = await caller.disableTotp(params);
    return {
      __action: "invalidate",
      scope: ["user"],
      success: true,
      ...result,
      message: `Disabled TOTP for user ${params.userId}.`,
    };
  },

  async list_notifications(params: { unreadOnly?: boolean; limit?: number }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    return caller.list({
      ...(params.unreadOnly !== undefined ? { unreadOnly: params.unreadOnly } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}),
    });
  },

  async mark_notification_read(params: { notificationId?: string }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    await caller.markRead({
      ...(params.notificationId !== undefined ? { id: params.notificationId } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      message: params.notificationId ? "Notification marked as read." : "All unread notifications marked as read.",
    };
  },

  async get_unread_notification_count(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const count = await caller.unreadCount();
    return { count };
  },

  async create_notification(params: {
    userId: string;
    type: string;
    title: string;
    body?: string;
    entityId?: string;
    entityType?: string;
    category?: "NOTIFICATION" | "REMINDER" | "TASK" | "APPROVAL";
    priority?: "LOW" | "NORMAL" | "HIGH" | "URGENT";
    link?: string;
    taskStatus?: "OPEN" | "IN_PROGRESS" | "DONE" | "DISMISSED";
    taskAction?: string;
    assigneeId?: string;
    dueDate?: string;
    channel?: "in_app" | "email" | "both";
    senderId?: string;
  }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const notification = await caller.create({
      userId: params.userId,
      type: params.type,
      title: params.title,
      ...(params.body !== undefined ? { body: params.body } : {}),
      ...(params.entityId !== undefined ? { entityId: params.entityId } : {}),
      ...(params.entityType !== undefined ? { entityType: params.entityType } : {}),
      ...(params.category !== undefined ? { category: params.category } : {}),
      ...(params.priority !== undefined ? { priority: params.priority } : {}),
      ...(params.link !== undefined ? { link: params.link } : {}),
      ...(params.taskStatus !== undefined ? { taskStatus: params.taskStatus } : {}),
      ...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}),
      ...(params.assigneeId !== undefined ? { assigneeId: params.assigneeId } : {}),
      ...(params.dueDate !== undefined ? { dueDate: new Date(params.dueDate) } : {}),
      ...(params.channel !== undefined ? { channel: params.channel } : {}),
      ...(params.senderId !== undefined ? { senderId: params.senderId } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      notification,
      notificationId: notification?.id ?? null,
      message: `Created notification "${params.title}".`,
    };
  },

  // ── DASHBOARD DETAIL ──

  async get_dashboard_detail(params: { section?: string }, ctx: ToolContext) {
    const caller = createDashboardCaller(createScopedCallerContext(ctx));
    return caller.getDetail({ ...(params.section ? { section: params.section } : {}) });
  },

  // ── PROJECT MANAGEMENT ──

  async delete_project(params: { projectId: string }, ctx: ToolContext) {
    assertPermission(ctx, "manageProjects" as PermissionKey);
    const project = await resolveProjectIdentifier(ctx, params.projectId);
    if ("error" in project) return project;

    const caller = createProjectCaller(createScopedCallerContext(ctx));
    await caller.delete({ id: project.id });
    return {
      __action: "invalidate",
      scope: ["project"],
      success: true,
      message: `Deleted project: ${project.name} (${project.shortCode})`,
    };
  },

  // ── ORG UNIT MANAGEMENT ──

  async create_org_unit(params: {
    name: string;
    shortName?: string;
    level: number;
    parentId?: string;
    sortOrder?: number;
  }, ctx: ToolContext) {
    const caller = createOrgUnitCaller(createScopedCallerContext(ctx));
    const ou = await caller.create(CreateOrgUnitSchema.parse(params));
    return { __action: "invalidate", scope: ["orgUnit"], success: true, message: `Created org unit: ${ou.name}`, orgUnitId: ou.id, orgUnit: ou };
  },

  async update_org_unit(params: {
    id: string;
    name?: string;
    shortName?: string | null;
    sortOrder?: number;
    isActive?: boolean;
    parentId?: string | null;
  }, ctx: ToolContext) {
    const caller = createOrgUnitCaller(createScopedCallerContext(ctx));
    const data = UpdateOrgUnitSchema.parse({
      ...(params.name !== undefined ? { name: params.name } : {}),
      ...(params.shortName !== undefined ? { shortName: params.shortName } : {}),
      ...(params.sortOrder !== undefined ? { sortOrder: params.sortOrder } : {}),
      ...(params.isActive !== undefined ? { isActive: params.isActive } : {}),
      ...(params.parentId !== undefined ? { parentId: params.parentId } : {}),
    });
    if (Object.keys(data).length === 0) return { error: "No fields to update" };
    const ou = await caller.update({ id: params.id, data });
    return { __action: "invalidate", scope: ["orgUnit"], success: true, message: `Updated org unit: ${ou.name}`, orgUnitId: ou.id, orgUnit: ou };
  },

  // ─── Cover Art ───────────────────────────────────────────────────────────

  async generate_project_cover(params: { projectId: string; prompt?: string }, ctx: ToolContext) {
    assertPermission(ctx, "manageProjects" as PermissionKey);
    const caller = createProjectCaller(createScopedCallerContext(ctx));
    const project = await resolveProjectIdentifier(ctx, params.projectId);
    if ("error" in project) {
      return project;
    }
    const { coverImageUrl } = await caller.generateCover({
      projectId: project.id,
      ...(params.prompt !== undefined ? { prompt: params.prompt } : {}),
    });

    return {
      __action: "invalidate",
      scope: ["project"],
      success: true,
      message: `Generated cover art for project "${project.name}"`,
      coverImageUrl: coverImageUrl.slice(0, 100) + "...[truncated]",
    };
  },

  async remove_project_cover(params: { projectId: string }, ctx: ToolContext) {
    assertPermission(ctx, "manageProjects" as PermissionKey);
    const caller = createProjectCaller(createScopedCallerContext(ctx));
    const project = await resolveProjectIdentifier(ctx, params.projectId);
    if ("error" in project) {
      return project;
    }
    await caller.removeCover({ projectId: project.id });

    return { __action: "invalidate", scope: ["project"], success: true, message: `Removed cover art from project "${project.name}"` };
  },

  // ── TASK MANAGEMENT ──

  async list_tasks(params: {
    status?: "OPEN" | "IN_PROGRESS" | "DONE" | "DISMISSED";
    includeAssigned?: boolean;
    limit?: number;
  }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    return caller.listTasks({
      ...(params.status !== undefined ? { status: params.status } : {}),
      ...(params.includeAssigned !== undefined ? { includeAssigned: params.includeAssigned } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}),
    });
  },

  async get_task_counts(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    return caller.taskCounts();
  },

  async get_task_detail(params: { taskId: string }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    return caller.getTaskDetail({ id: params.taskId });
  },

  async update_task_status(params: { taskId: string; status: string }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const task = await caller.updateTaskStatus({
      id: params.taskId,
      status: params.status as "OPEN" | "IN_PROGRESS" | "DONE" | "DISMISSED",
    });
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      task,
      message: `Task status updated to ${task.taskStatus ?? params.status}.`,
    };
  },

  async execute_task_action(params: { taskId: string }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const result = await caller.executeTaskAction({ id: params.taskId });

    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      task: result.task,
      message: result.actionResult.message,
    };
  },

  async create_reminder(params: {
    title: string;
    body?: string;
    remindAt: string;
    recurrence?: string;
    entityId?: string;
    entityType?: string;
    link?: string;
  }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const reminder = await caller.createReminder({
      title: params.title,
      remindAt: new Date(params.remindAt),
      ...(params.body !== undefined ? { body: params.body } : {}),
      ...(params.recurrence !== undefined ? { recurrence: params.recurrence as "daily" | "weekly" | "monthly" } : {}),
      ...(params.entityId !== undefined ? { entityId: params.entityId } : {}),
      ...(params.entityType !== undefined ? { entityType: params.entityType } : {}),
      ...(params.link !== undefined ? { link: params.link } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      reminder,
      reminderId: reminder.id,
      message: `Reminder "${params.title}" created.`,
    };
  },

  async list_reminders(params: { limit?: number }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    return caller.listReminders({
      ...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}),
    });
  },

  async update_reminder(params: {
    id: string;
    title?: string;
    body?: string;
    remindAt?: string;
    recurrence?: "daily" | "weekly" | "monthly" | null;
  }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const reminder = await caller.updateReminder({
      id: params.id,
      ...(params.title !== undefined ? { title: params.title } : {}),
      ...(params.body !== undefined ? { body: params.body } : {}),
      ...(params.remindAt !== undefined ? { remindAt: new Date(params.remindAt) } : {}),
      ...(params.recurrence !== undefined ? { recurrence: params.recurrence } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      reminder,
      reminderId: reminder.id,
      message: `Updated reminder ${params.id}.`,
    };
  },

  async delete_reminder(params: { id: string }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    await caller.deleteReminder({ id: params.id });
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      id: params.id,
      message: `Deleted reminder ${params.id}.`,
    };
  },

  async create_task_for_user(params: {
    userId: string;
    title: string;
    body?: string;
    priority?: string;
    dueDate?: string;
    taskAction?: string;
    entityId?: string;
    entityType?: string;
    link?: string;
    channel?: "in_app" | "email" | "both";
  }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const task = await caller.createTask({
      userId: params.userId,
      title: params.title,
      ...(params.body !== undefined ? { body: params.body } : {}),
      ...(params.priority !== undefined ? { priority: params.priority as "LOW" | "NORMAL" | "HIGH" | "URGENT" } : {}),
      ...(params.dueDate !== undefined ? { dueDate: new Date(params.dueDate) } : {}),
      ...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}),
      ...(params.entityId !== undefined ? { entityId: params.entityId } : {}),
      ...(params.entityType !== undefined ? { entityType: params.entityType } : {}),
      ...(params.link !== undefined ? { link: params.link } : {}),
      ...(params.channel !== undefined ? { channel: params.channel } : {}),
    });
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      task,
      taskId: task?.id ?? null,
      message: `Created task "${params.title}" for ${params.userId}.`,
    };
  },

  async assign_task(params: { id: string; assigneeId: string }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const task = await caller.assignTask(params);
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      task,
      taskId: task.id,
      message: `Assigned task ${params.id} to ${params.assigneeId}.`,
    };
  },

  async send_broadcast(params: {
    title: string;
    body?: string;
    targetType: string;
    targetValue?: string;
    category?: "NOTIFICATION" | "REMINDER" | "TASK" | "APPROVAL";
    priority?: string;
    channel?: string;
    link?: string;
    scheduledAt?: string;
    taskAction?: string;
    dueDate?: string;
  }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    const broadcast = await caller.createBroadcast({
      title: params.title,
      targetType: params.targetType as "user" | "role" | "project" | "orgUnit" | "all",
      ...(params.body !== undefined ? { body: params.body } : {}),
      ...(params.link !== undefined ? { link: params.link } : {}),
      ...(params.category !== undefined ? { category: params.category } : {}),
      ...(params.priority !== undefined ? { priority: params.priority as "LOW" | "NORMAL" | "HIGH" | "URGENT" } : {}),
      ...(params.channel !== undefined ? { channel: params.channel as "in_app" | "email" | "both" } : {}),
      ...(params.targetValue !== undefined ? { targetValue: params.targetValue } : {}),
      ...(params.scheduledAt !== undefined ? { scheduledAt: new Date(params.scheduledAt) } : {}),
      ...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}),
      ...(params.dueDate !== undefined ? { dueDate: new Date(params.dueDate) } : {}),
    });

    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      broadcast,
      broadcastId: broadcast.id,
      recipientCount: broadcast.recipientCount ?? 0,
      message: `Broadcast "${params.title}" created.`,
    };
  },

  async list_broadcasts(params: { limit?: number }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    return caller.listBroadcasts({
      ...(params.limit !== undefined ? { limit: Math.min(params.limit, 50) } : {}),
    });
  },

  async get_broadcast_detail(params: { id: string }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    return caller.getBroadcastById({ id: params.id });
  },

  async delete_notification(params: { id: string }, ctx: ToolContext) {
    const caller = createNotificationCaller(createScopedCallerContext(ctx));
    await caller.delete({ id: params.id });
    return {
      __action: "invalidate",
      scope: ["notification"],
      success: true,
      id: params.id,
      message: `Deleted notification ${params.id}.`,
    };
  },

  // ── INSIGHTS & ANOMALIES ──────────────────────────────────────────────────

  async detect_anomalies(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createInsightsCaller(createScopedCallerContext(ctx));
    return caller.getAnomalyDetail();
  },

  async get_skill_gaps(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createDashboardCaller(createScopedCallerContext(ctx));
    return caller.getSkillGapSummary();
  },

  async get_project_health(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createDashboardCaller(createScopedCallerContext(ctx));
    return caller.getProjectHealthDetail();
  },

  async get_budget_forecast(_params: Record<string, never>, ctx: ToolContext) {
    assertPermission(ctx, "viewCosts" as PermissionKey);

    const caller = createDashboardCaller(createScopedCallerContext(ctx));
    return caller.getBudgetForecastDetail();
  },

  async get_insights_summary(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createInsightsCaller(createScopedCallerContext(ctx));
    return caller.getInsightsSummary();
  },

  async run_report(params: {
    entity: string;
    columns: string[];
    filters?: Array<{
      field: string;
      op: "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "contains" | "in";
      value: string;
    }>;
    periodMonth?: string;
    limit?: number;
  }, ctx: ToolContext) {
    const entity = params.entity as "resource" | "project" | "assignment" | "resource_month";
    if (!["resource", "project", "assignment", "resource_month"].includes(entity)) {
      return {
        error:
          `Unknown entity: ${params.entity}. Use resource, project, assignment, or resource_month.`,
      };
    }
    const caller = createReportCaller(createScopedCallerContext(ctx));
    const result = await caller.getReportData({
      entity,
      columns: params.columns,
      filters: params.filters ?? [],
      periodMonth: params.periodMonth,
      sortDir: "asc",
      limit: Math.min(params.limit ?? 50, 200),
      offset: 0,
    });

    return {
      rows: result.rows,
      rowCount: result.rows.length,
      columns: result.columns,
    };
  },

  async list_comments(params: { entityType: string; entityId: string }, ctx: ToolContext) {
    const caller = createCommentCaller(createScopedCallerContext(ctx));
    const comments = await caller.list({
      entityType: params.entityType,
      entityId: params.entityId,
    });

    return comments.map((c) => ({
      id: c.id,
      author: c.author.name ?? c.author.email,
      body: c.body,
      resolved: c.resolved,
      createdAt: c.createdAt.toISOString(),
      replyCount: c.replies.length,
      replies: c.replies.map((r) => ({
        id: r.id,
        author: r.author.name ?? r.author.email,
        body: r.body,
        resolved: r.resolved,
        createdAt: r.createdAt.toISOString(),
      })),
    }));
  },

  async lookup_rate(params: {
    clientId?: string;
    chapter?: string;
    managementLevelId?: string;
    roleName?: string;
    seniority?: string;
  }, ctx: ToolContext) {
    assertPermission(ctx, "viewCosts" as PermissionKey);
    const caller = createRateCardCaller(createScopedCallerContext(ctx));
    const result = await caller.lookupBestMatch(params);

    return {
      ...(result.message ? { message: result.message } : {}),
      bestMatch: result.bestMatch
        ? {
            ...result.bestMatch,
            costRate: fmtEur(result.bestMatch.costRateCents),
            billRate: result.bestMatch.billRateCents ? fmtEur(result.bestMatch.billRateCents) : null,
          }
        : null,
      alternatives: result.alternatives.map((alternative) => ({
        ...alternative,
        costRate: fmtEur(alternative.costRateCents),
        billRate: alternative.billRateCents ? fmtEur(alternative.billRateCents) : null,
      })),
      totalCandidates: result.totalCandidates,
    };
  },

  // ── SCENARIO & AI ─────────────────────────────────────────────────────────

  async simulate_scenario(params: {
    projectId: string;
    changes: Array<{
      assignmentId?: string;
      resourceId?: string;
      roleId?: string;
      startDate: string;
      endDate: string;
      hoursPerDay: number;
      remove?: boolean;
    }>;
  }, ctx: ToolContext) {
    const caller = createScenarioCaller(createScopedCallerContext(ctx));
    const result = await caller.simulate({
      projectId: params.projectId,
      changes: params.changes.map((change) => ({
        ...change,
        startDate: new Date(change.startDate),
        endDate: new Date(change.endDate),
      })),
    });

    return {
      baseline: {
        ...result.baseline,
        totalCost: fmtEur(result.baseline.totalCostCents),
      },
      scenario: {
        ...result.scenario,
        totalCost: fmtEur(result.scenario.totalCostCents),
      },
      delta: {
        ...result.delta,
        cost: fmtEur(result.delta.costCents),
      },
      resourceImpacts: result.resourceImpacts,
      warnings: result.warnings,
      budgetCents: result.budgetCents,
    };
  },

  async generate_project_narrative(params: { projectId: string }, ctx: ToolContext) {
    const caller = createInsightsCaller(createScopedCallerContext(ctx));
    return caller.generateProjectNarrative({ projectId: params.projectId });
  },

  async create_comment(params: {
    entityType: string;
    entityId: string;
    body: string;
  }, ctx: ToolContext) {
    const caller = createCommentCaller(createScopedCallerContext(ctx));
    const comment = await caller.create({
      entityType: params.entityType,
      entityId: params.entityId,
      body: params.body,
    });

    return {
      __action: "invalidate",
      scope: ["comment"],
      id: comment.id,
      author: comment.author.name ?? comment.author.email,
      body: comment.body,
      createdAt: comment.createdAt.toISOString(),
    };
  },

  async resolve_comment(params: {
    commentId: string;
    resolved?: boolean;
  }, ctx: ToolContext) {
    const caller = createCommentCaller(createScopedCallerContext(ctx));
    const updated = await caller.resolve({
      id: params.commentId,
      resolved: params.resolved !== false,
    });

    return {
      __action: "invalidate",
      scope: ["comment"],
      id: updated.id,
      resolved: updated.resolved,
      author: updated.author.name ?? updated.author.email,
      body: updated.body.slice(0, 100),
    };
  },

  async query_change_history(params: {
    entityType?: string;
    search?: string;
    userId?: string;
    daysBack?: number;
    action?: string;
    limit?: number;
  }, ctx: ToolContext) {
    const limit = Math.min(params.limit ?? 20, 50);
    const daysBack = params.daysBack ?? 7;
    const startDate = new Date();
    startDate.setDate(startDate.getDate() - daysBack);

    const caller = createAuditLogCaller(createScopedCallerContext(ctx));
    const result = await caller.listDetail({
      ...(params.entityType ? { entityType: params.entityType } : {}),
      ...(params.userId ? { userId: params.userId } : {}),
      ...(params.action ? { action: params.action } : {}),
      ...(params.search ? { search: params.search } : {}),
      startDate,
      limit,
    });

    return {
      filters: {
        entityType: params.entityType ?? null,
        userId: params.userId ?? null,
        action: params.action ?? null,
        search: params.search ?? null,
        daysBack,
      },
      itemCount: result.items.length,
      nextCursor: result.nextCursor ?? null,
      items: result.items,
    };
  },

  async get_entity_timeline(params: {
    entityType: string;
    entityId: string;
    limit?: number;
  }, ctx: ToolContext) {
    const limit = Math.min(params.limit ?? 50, 200);
    const caller = createAuditLogCaller(createScopedCallerContext(ctx));
    return caller.getByEntityDetail({
      entityType: params.entityType,
      entityId: params.entityId,
      limit,
    });
  },

  async export_resources_csv(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createImportExportCaller(createScopedCallerContext(ctx));
    const csv = await caller.exportResourcesCSV();
    return {
      format: "csv",
      lineCount: csv.length === 0 ? 0 : csv.split("\n").length,
      csv,
    };
  },

  async export_projects_csv(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createImportExportCaller(createScopedCallerContext(ctx));
    const csv = await caller.exportProjectsCSV();
    return {
      format: "csv",
      lineCount: csv.length === 0 ? 0 : csv.split("\n").length,
      csv,
    };
  },

  async import_csv_data(params: {
    entityType: "resources" | "projects" | "allocations";
    rows: Array<Record<string, string>>;
    dryRun?: boolean;
  }, ctx: ToolContext) {
    assertPermission(ctx, PermissionKey.IMPORT_DATA);
    const caller = createImportExportCaller(createScopedCallerContext(ctx));
    return caller.importCSV({
      entityType: params.entityType,
      rows: params.rows,
      dryRun: params.dryRun ?? true,
    });
  },

  async list_dispo_import_batches(params: {
    status?: ImportBatchStatus;
    limit?: number;
    cursor?: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.listImportBatches({
      ...(params.status ? { status: params.status } : {}),
      ...(params.cursor ? { cursor: params.cursor } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
    });
  },

  async get_dispo_import_batch(params: {
    id: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.getImportBatch({ id: params.id });
  },

  async stage_dispo_import_batch(params: {
    chargeabilityWorkbookPath: string;
    costWorkbookPath?: string;
    notes?: string | null;
    planningWorkbookPath: string;
    referenceWorkbookPath: string;
    rosterWorkbookPath?: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.stageImportBatch({
      chargeabilityWorkbookPath: params.chargeabilityWorkbookPath,
      planningWorkbookPath: params.planningWorkbookPath,
      referenceWorkbookPath: params.referenceWorkbookPath,
      ...(params.costWorkbookPath !== undefined ? { costWorkbookPath: params.costWorkbookPath } : {}),
      ...(params.notes !== undefined ? { notes: params.notes } : {}),
      ...(params.rosterWorkbookPath !== undefined ? { rosterWorkbookPath: params.rosterWorkbookPath } : {}),
    });
  },

  async validate_dispo_import_batch(params: {
    chargeabilityWorkbookPath: string;
    costWorkbookPath?: string;
    importBatchId?: string;
    notes?: string | null;
    planningWorkbookPath: string;
    referenceWorkbookPath: string;
    rosterWorkbookPath?: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.validateImportBatch({
      chargeabilityWorkbookPath: params.chargeabilityWorkbookPath,
      planningWorkbookPath: params.planningWorkbookPath,
      referenceWorkbookPath: params.referenceWorkbookPath,
      ...(params.costWorkbookPath !== undefined ? { costWorkbookPath: params.costWorkbookPath } : {}),
      ...(params.importBatchId !== undefined ? { importBatchId: params.importBatchId } : {}),
      ...(params.notes !== undefined ? { notes: params.notes } : {}),
      ...(params.rosterWorkbookPath !== undefined ? { rosterWorkbookPath: params.rosterWorkbookPath } : {}),
    });
  },

  async cancel_dispo_import_batch(params: {
    id: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.cancelImportBatch({ id: params.id });
  },

  async list_dispo_staged_resources(params: {
    importBatchId: string;
    status?: StagedRecordStatus;
    limit?: number;
    cursor?: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.listStagedResources({
      importBatchId: params.importBatchId,
      ...(params.status !== undefined ? { status: params.status } : {}),
      ...(params.cursor ? { cursor: params.cursor } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
    });
  },

  async list_dispo_staged_projects(params: {
    importBatchId: string;
    status?: StagedRecordStatus;
    isTbd?: boolean;
    limit?: number;
    cursor?: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.listStagedProjects({
      importBatchId: params.importBatchId,
      ...(params.status !== undefined ? { status: params.status } : {}),
      ...(params.isTbd !== undefined ? { isTbd: params.isTbd } : {}),
      ...(params.cursor ? { cursor: params.cursor } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
    });
  },

  async list_dispo_staged_assignments(params: {
    importBatchId: string;
    status?: StagedRecordStatus;
    resourceExternalId?: string;
    limit?: number;
    cursor?: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.listStagedAssignments({
      importBatchId: params.importBatchId,
      ...(params.status !== undefined ? { status: params.status } : {}),
      ...(params.resourceExternalId !== undefined ? { resourceExternalId: params.resourceExternalId } : {}),
      ...(params.cursor ? { cursor: params.cursor } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
    });
  },

  async list_dispo_staged_vacations(params: {
    importBatchId: string;
    resourceExternalId?: string;
    limit?: number;
    cursor?: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.listStagedVacations({
      importBatchId: params.importBatchId,
      ...(params.resourceExternalId !== undefined ? { resourceExternalId: params.resourceExternalId } : {}),
      ...(params.cursor ? { cursor: params.cursor } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
    });
  },

  async list_dispo_staged_unresolved_records(params: {
    importBatchId: string;
    recordType?: DispoStagedRecordType;
    limit?: number;
    cursor?: string;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.listStagedUnresolvedRecords({
      importBatchId: params.importBatchId,
      ...(params.recordType !== undefined ? { recordType: params.recordType } : {}),
      ...(params.cursor ? { cursor: params.cursor } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
    });
  },

  async resolve_dispo_staged_record(params: {
    action: "APPROVE" | "REJECT" | "SKIP";
    id: string;
    recordType: DispoStagedRecordType;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.resolveStagedRecord({
      action: params.action,
      id: params.id,
      recordType: params.recordType,
    });
  },

  async commit_dispo_import_batch(params: {
    allowTbdUnresolved?: boolean;
    importBatchId: string;
    importTbdProjects?: boolean;
  }, ctx: ToolContext) {
    const caller = createDispoCaller(createScopedCallerContext(ctx));
    return caller.commitImportBatch({
      importBatchId: params.importBatchId,
      ...(params.allowTbdUnresolved !== undefined ? { allowTbdUnresolved: params.allowTbdUnresolved } : {}),
      ...(params.importTbdProjects !== undefined ? { importTbdProjects: params.importTbdProjects } : {}),
    });
  },

  async get_system_settings(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createSettingsCaller(createScopedCallerContext(ctx));
    return caller.getSystemSettings();
  },

  async update_system_settings(params: {
    aiProvider?: "openai" | "azure";
    azureOpenAiEndpoint?: string;
    azureOpenAiDeployment?: string;
    azureOpenAiApiKey?: string;
    azureApiVersion?: string;
    aiMaxCompletionTokens?: number;
    aiTemperature?: number;
    aiSummaryPrompt?: string;
    scoreWeights?: {
      skillDepth: number;
      skillBreadth: number;
      costEfficiency: number;
      chargeability: number;
      experience: number;
    };
    scoreVisibleRoles?: SystemRole[];
    smtpHost?: string;
    smtpPort?: number;
    smtpUser?: string;
    smtpPassword?: string;
    smtpFrom?: string;
    smtpTls?: boolean;
    anonymizationEnabled?: boolean;
    anonymizationDomain?: string;
    anonymizationSeed?: string;
    anonymizationMode?: "global";
    azureDalleDeployment?: string;
    azureDalleEndpoint?: string;
    azureDalleApiKey?: string;
    geminiApiKey?: string;
    geminiModel?: string;
    imageProvider?: "dalle" | "gemini";
    vacationDefaultDays?: number;
    timelineUndoMaxSteps?: number;
  }, ctx: ToolContext) {
    const caller = createSettingsCaller(createScopedCallerContext(ctx));
    return caller.updateSystemSettings(params);
  },

  async test_ai_connection(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createSettingsCaller(createScopedCallerContext(ctx));
    return caller.testAiConnection();
  },

  async test_smtp_connection(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createSettingsCaller(createScopedCallerContext(ctx));
    return caller.testSmtpConnection();
  },

  async test_gemini_connection(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createSettingsCaller(createScopedCallerContext(ctx));
    return caller.testGeminiConnection();
  },

  async get_ai_configured(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createSettingsCaller(createScopedCallerContext(ctx));
    return caller.getAiConfigured();
  },

  async list_system_role_configs(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createSystemRoleConfigCaller(createScopedCallerContext(ctx));
    return caller.list();
  },

  async update_system_role_config(params: {
    role: string;
    label?: string;
    description?: string | null;
    color?: string | null;
    defaultPermissions?: string[];
  }, ctx: ToolContext) {
    const caller = createSystemRoleConfigCaller(createScopedCallerContext(ctx));
    return caller.update(params);
  },

  async list_webhooks(_params: Record<string, never>, ctx: ToolContext) {
    const caller = createWebhookCaller(createScopedCallerContext(ctx));
    const webhooks = await caller.list();
    return sanitizeWebhookList(webhooks);
  },

  async get_webhook(params: {
    id: string;
  }, ctx: ToolContext) {
    const caller = createWebhookCaller(createScopedCallerContext(ctx));
    const webhook = await caller.getById({ id: params.id });
    return sanitizeWebhook(webhook);
  },

  async create_webhook(params: {
    name: string;
    url: string;
    secret?: string;
    events: string[];
    isActive?: boolean;
  }, ctx: ToolContext) {
    const caller = createWebhookCaller(createScopedCallerContext(ctx));
    const webhook = await caller.create({
      name: params.name,
      url: params.url,
      events: params.events as [string, ...string[]],
      ...(params.secret !== undefined ? { secret: params.secret } : {}),
      ...(params.isActive !== undefined ? { isActive: params.isActive } : {}),
    });
    return sanitizeWebhook(webhook);
  },

  async update_webhook(params: {
    id: string;
    data: {
      name?: string;
      url?: string;
      secret?: string | null;
      events?: string[];
      isActive?: boolean;
    };
  }, ctx: ToolContext) {
    const caller = createWebhookCaller(createScopedCallerContext(ctx));
    const webhook = await caller.update({
      id: params.id,
      data: {
        ...(params.data.name !== undefined ? { name: params.data.name } : {}),
        ...(params.data.url !== undefined ? { url: params.data.url } : {}),
        ...(params.data.secret !== undefined ? { secret: params.data.secret } : {}),
        ...(params.data.events !== undefined ? { events: params.data.events as [string, ...string[]] } : {}),
        ...(params.data.isActive !== undefined ? { isActive: params.data.isActive } : {}),
      },
    });
    return sanitizeWebhook(webhook);
  },

  async delete_webhook(params: {
    id: string;
  }, ctx: ToolContext) {
    const caller = createWebhookCaller(createScopedCallerContext(ctx));
    await caller.delete({ id: params.id });
    return { ok: true, id: params.id };
  },

  async test_webhook(params: {
    id: string;
  }, ctx: ToolContext) {
    const caller = createWebhookCaller(createScopedCallerContext(ctx));
    return caller.test({ id: params.id });
  },

  async list_audit_log_entries(params: {
    entityType?: string;
    entityId?: string;
    userId?: string;
    action?: string;
    source?: string;
    startDate?: string;
    endDate?: string;
    search?: string;
    limit?: number;
    cursor?: string;
  }, ctx: ToolContext) {
    const caller = createAuditLogCaller(createScopedCallerContext(ctx));
    const result = await caller.listDetail({
      ...(params.entityType ? { entityType: params.entityType } : {}),
      ...(params.entityId ? { entityId: params.entityId } : {}),
      ...(params.userId ? { userId: params.userId } : {}),
      ...(params.action ? { action: params.action } : {}),
      ...(params.source ? { source: params.source } : {}),
      ...(params.startDate ? { startDate: parseIsoDate(params.startDate, "startDate") } : {}),
      ...(params.endDate ? { endDate: parseIsoDate(params.endDate, "endDate") } : {}),
      ...(params.search ? { search: params.search } : {}),
      ...(params.cursor ? { cursor: params.cursor } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 100) } : {}),
    });

    return {
      filters: {
        entityType: params.entityType ?? null,
        entityId: params.entityId ?? null,
        userId: params.userId ?? null,
        action: params.action ?? null,
        source: params.source ?? null,
        startDate: params.startDate ?? null,
        endDate: params.endDate ?? null,
        search: params.search ?? null,
      },
      itemCount: result.items.length,
      nextCursor: result.nextCursor ?? null,
      items: result.items,
    };
  },

  async get_audit_log_entry(params: {
    id: string;
  }, ctx: ToolContext) {
    const caller = createAuditLogCaller(createScopedCallerContext(ctx));
    return caller.getByIdDetail({ id: params.id });
  },

  async get_audit_log_timeline(params: {
    startDate?: string;
    endDate?: string;
    limit?: number;
  }, ctx: ToolContext) {
    const caller = createAuditLogCaller(createScopedCallerContext(ctx));
    return caller.getTimelineDetail({
      ...(params.startDate ? { startDate: parseIsoDate(params.startDate, "startDate") } : {}),
      ...(params.endDate ? { endDate: parseIsoDate(params.endDate, "endDate") } : {}),
      ...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 500) } : {}),
    });
  },

  async get_audit_activity_summary(params: {
    startDate?: string;
    endDate?: string;
  }, ctx: ToolContext) {
    const caller = createAuditLogCaller(createScopedCallerContext(ctx));
    return caller.getActivitySummary({
      ...(params.startDate ? { startDate: parseIsoDate(params.startDate, "startDate") } : {}),
      ...(params.endDate ? { endDate: parseIsoDate(params.endDate, "endDate") } : {}),
    });
  },

  async get_shoring_ratio(params: { projectId: string }, ctx: ToolContext) {
    const project = await resolveProjectIdentifier(ctx, params.projectId);
    if ("error" in project) return project;

    const caller = createProjectCaller(createScopedCallerContext(ctx));
    const result = await caller.getShoringRatio({ projectId: project.id });

    if (result.totalHours <= 0) {
      return `Project "${project.name}" (${project.shortCode}): No active assignments — shoring ratio not available.`;
    }

    const countryParts = Object.entries(result.byCountry)
      .sort((a, b) => b[1].pct - a[1].pct)
      .map(([code, info]) => `${code} ${info.pct}% (${info.resourceCount} people)`)
      .join(", ");

    const status = result.offshoreRatio >= result.threshold
      ? `Target met (>=${result.threshold}% offshore)`
      : result.offshoreRatio >= result.threshold - 10
        ? `Close to target (${result.threshold}% offshore needed)`
        : `Below target — only ${result.offshoreRatio}% offshore, need ${result.threshold}%`;

    return `Project "${project.name}" (${project.shortCode}): ${result.onshoreRatio}% onshore (${result.onshoreCountryCode}), ${result.offshoreRatio}% offshore. ${status}. Breakdown: ${countryParts}.${result.unknownCount > 0 ? ` (${result.unknownCount} resource(s) without country)` : ""}`;
  },
};

// ─── Executor ───────────────────────────────────────────────────────────────

export interface ToolAction {
  type: string;
  url?: string;
  scope?: string[];
  description?: string;
}

export interface ToolResult {
  content: string;
  action?: ToolAction;
  data?: unknown;
}

export async function executeTool(
  name: string,
  args: string,
  ctx: ToolContext,
): Promise<ToolResult> {
  const executor = executors[name as keyof typeof executors];
  if (!executor) return { content: JSON.stringify({ error: `Unknown tool: ${name}` }) };

  try {
    const params = JSON.parse(args);

    // Audit-log all mutation tool executions (EGAI 4.1.3.1 / IAAI 3.6.26)
    if (MUTATION_TOOLS.has(name)) {
      logger.info(
        { tool: name, params, userId: ctx.userId, userRole: ctx.userRole },
        "AI assistant mutation tool executed",
      );
    }

    const result = await executor(params, ctx);

    // Detect action payloads (e.g. navigation, invalidation)
    if (result && typeof result === "object" && "__action" in (result as Record<string, unknown>)) {
      const actionResult = result as Record<string, unknown>;
      const actionType = actionResult.__action as string;

      if (actionType === "navigate") {
        const url = actionResult.url as string;
        const desc = (actionResult.description as string | undefined) ?? url;
        return {
          content: JSON.stringify({ description: desc }),
          data: { description: desc },
          action: { type: "navigate", url, description: desc },
        };
      }

      if (actionType === "invalidate") {
        const scope = actionResult.scope as string[];
        // Strip __action, scope, and large data from the result sent back to the AI
        const { __action: _, scope: _s, coverImageUrl: _img, ...rest } = actionResult;
        const content = JSON.stringify(rest);
        return {
          content: content.length > 4000 ? content.slice(0, 4000) + '..."' : content,
          data: rest,
          action: { type: "invalidate", scope },
        };
      }
    }

    // Cap tool result size to prevent oversized OpenAI conversation payloads
    const content = typeof result === "string" ? result : JSON.stringify(result);
    return {
      content: content.length > 8000 ? content.slice(0, 8000) + '..."' : content,
      ...(typeof result === "string" ? {} : { data: result }),
    };
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    logger.error(
      {
        tool: name,
        userId: ctx.userId,
        userRole: ctx.userRole,
        error: err instanceof Error ? { message: err.message, stack: err.stack } : err,
      },
      "AI assistant tool execution failed",
    );
    return { content: JSON.stringify({ error: msg }) };
  }
}
