/**
 * Canonical tool metadata (title, description, inputSchema, annotations) declared once.
 * Consumed by both `registerTools` (raw Zod) and `buildMcpServerCard` (toJsonSchemaCompat).
 */
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
import z from "zod";
import { resumePatchOperationsSchema } from "@reactive-resume/ai/tools/resume-tool-contracts";
import { applicationStatusSchema, contactSchema } from "@reactive-resume/schema/applications/data";
import { MCP_TOOL_NAME as T } from "./mcp-tool-names";

const MAX_APPLICATION_DOCUMENT_BYTES = 10 * 1024 * 1024;
const READ_IDEMPOTENT: ToolAnnotations = {
	readOnlyHint: true,
	destructiveHint: false,
	idempotentHint: true,
	openWorldHint: false,
};
const READ_NON_IDEMPOTENT: ToolAnnotations = {
	readOnlyHint: true,
	destructiveHint: false,
	idempotentHint: false,
	openWorldHint: false,
};
const READ_OPEN_WORLD_NON_IDEMPOTENT: ToolAnnotations = {
	readOnlyHint: true,
	destructiveHint: false,
	idempotentHint: false,
	openWorldHint: true,
};
const WRITE_NON_IDEMPOTENT: ToolAnnotations = {
	readOnlyHint: false,
	destructiveHint: false,
	idempotentHint: false,
	openWorldHint: false,
};
const WRITE_DESTRUCTIVE: ToolAnnotations = {
	readOnlyHint: false,
	destructiveHint: true,
	idempotentHint: true,
	openWorldHint: false,
};
const WRITE_IDEMPOTENT: ToolAnnotations = {
	readOnlyHint: false,
	destructiveHint: false,
	idempotentHint: true,
	openWorldHint: false,
};

// ponytail: shared schema fragment; exported so server-card can re-use without re-importing
const resumeIdSchema = z.string().min(1).describe(`Resume ID. Use \`${T.listResumes}\` to find valid IDs.`);
const applicationIdSchema = z
	.string()
	.min(1)
	.describe(`Application ID. Use \`${T.listApplications}\` to find valid IDs.`);
const applicationTimelineEntryIdSchema = z.string().min(1).describe("Timeline entry ID from an application response.");
const applicationDocumentKindSchema = z.enum(["resume", "cover-letter"]);
const timelineDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must use YYYY-MM-DD format.");
const httpUrlSchema = z
	.string()
	.trim()
	.pipe(z.url({ protocol: /^https?$/, error: "URL must use http or https." }));
const pdfBase64Schema = z
	.string()
	.min(1)
	.refine((value) => Buffer.from(value, "base64").byteLength <= MAX_APPLICATION_DOCUMENT_BYTES, {
		message: "Decoded PDF must be 10MB or smaller.",
	})
	.describe("Base64-encoded PDF bytes. Only application/pdf documents up to 10MB are accepted.");

const applicationMutableFieldsSchema = {
	company: z.string().min(1).optional().describe("Company name."),
	role: z.string().min(1).optional().describe("Role or job title."),
	status: applicationStatusSchema.optional().describe("Pipeline stage."),
	location: z.string().nullable().optional(),
	salary: z.string().nullable().optional(),
	source: z.string().nullable().optional(),
	sourceUrl: httpUrlSchema.nullable().optional(),
	jobDescription: z.string().max(20_000).nullable().optional(),
	notes: z.string().nullable().optional(),
	resumeId: z.string().nullable().optional(),
	resumeFileUrl: z.string().nullable().optional(),
	resumeFileName: z.string().nullable().optional(),
	coverLetterUrl: z.string().nullable().optional(),
	coverLetterName: z.string().nullable().optional(),
	followUpAt: z
		.string()
		.datetime({ offset: true })
		.nullable()
		.optional()
		.describe("Follow-up timestamp in ISO 8601 format."),
	followUpNote: z.string().nullable().optional(),
	contacts: z.array(contactSchema).optional(),
	tags: z.array(z.string()).optional(),
} as const;

const createApplicationSchema = z
	.object({
		...applicationMutableFieldsSchema,
		company: z.string().min(1).describe("Company name."),
		role: z.string().min(1).describe("Role or job title."),
		stageEnteredAt: timelineDateSchema.optional().describe("Initial stage date in YYYY-MM-DD format."),
	})
	.strict();

export const TOOL_META = {
	[T.listResumes]: {
		title: "List Resumes",
		description: [
			"Primary way to discover resume IDs for this account. Resumes are not listed as MCP resources;",
			"use this tool (not `resources/list`) to enumerate IDs.",
			"",
			"Returns an array of resume objects (without full resume data) containing:",
			"id, name, slug, tags, isPublic, isLocked, createdAt, updatedAt.",
			"",
			`Call this before \`${T.getResume}\`, \`${T.patchResume}\`, prompts, or \`resources/read\` with \`resume://{id}\`.`,
			"Results can be filtered by tags and sorted by last updated date, creation date, or name.",
		].join("\n"),
		inputSchema: z.object({
			tags: z
				.array(z.string())
				.optional()
				.default([])
				.describe("Filter resumes by tags. Only resumes matching ALL specified tags are returned. Default: no filter."),
			sort: z
				.enum(["lastUpdatedAt", "createdAt", "name"])
				.optional()
				.default("lastUpdatedAt")
				.describe("Sort order for results. Default: lastUpdatedAt."),
		}),
		annotations: READ_IDEMPOTENT,
	},
	[T.listResumeTags]: {
		title: "List Resume Tags",
		description: [
			"Returns a sorted list of every distinct tag used across your resumes.",
			"Useful for choosing tag filters when calling list tools or keeping naming consistent.",
		].join("\n"),
		inputSchema: z.object({}),
		annotations: READ_IDEMPOTENT,
	},
	[T.getResume]: {
		title: "Read Resume",
		description: [
			"Get the full data of a specific resume by its ID.",
			"",
			"Returns the complete resume data as JSON, including: basics (name, headline, email, phone,",
			"location, website), summary, picture settings, all sections (experience, education, skills,",
			"projects, etc.), custom sections, and metadata (template, layout, typography, colors).",
			"",
			`Use \`${T.listResumes}\` first to find valid IDs.`,
			"The `resume://_meta/schema` resource describes the full data structure for JSON Patch paths.",
		].join("\n"),
		inputSchema: z.object({ id: resumeIdSchema }),
		annotations: READ_IDEMPOTENT,
	},
	[T.getResumeAnalysis]: {
		title: "Get Resume Analysis",
		description: [
			"Returns the latest saved AI analysis for a resume (scorecard, strengths, suggestions), if any.",
			"Analyses are created from the Reactive Resume web app AI flow, not from MCP.",
			`Returns JSON or a short message if none exists. Use \`${T.listResumes}\` to find resume IDs.`,
		].join("\n"),
		inputSchema: z.object({ id: resumeIdSchema }),
		annotations: READ_IDEMPOTENT,
	},
	[T.downloadResumePdf]: {
		title: "Download Resume PDF",
		description: [
			"Create a short-lived authenticated URL for downloading a resume as a PDF.",
			"The URL expires in 10 minutes and should be used immediately.",
			"Returns JSON containing: resumeId, name, downloadUrl, expiresAt, expiresInSeconds, contentType.",
			`Use \`${T.listResumes}\` first to find valid IDs.`,
		].join("\n"),
		inputSchema: z.object({ id: resumeIdSchema }),
		annotations: READ_NON_IDEMPOTENT,
	},
	[T.createResume]: {
		title: "Create Resume",
		description: [
			"Create a new, empty resume with a name and URL-friendly slug.",
			"",
			"Returns the ID of the newly created resume.",
			"Set `withSampleData` to true to pre-fill with example content (useful for testing).",
			`After creating, use \`${T.getResume}\` to view or \`${T.patchResume}\` to populate it.`,
		].join("\n"),
		inputSchema: z.object({
			name: z.string().min(1).max(64).describe("Display name for the resume (e.g. 'Software Engineer 2026')"),
			slug: z
				.string()
				.min(1)
				.max(64)
				.describe("URL-friendly slug, must be unique across your resumes (e.g. 'software-engineer-2026')"),
			tags: z
				.array(z.string())
				.optional()
				.default([])
				.describe("Tags to categorize the resume (e.g. ['tech', 'senior'])"),
			withSampleData: z.boolean().optional().default(false).describe("Pre-fill with sample data. Default: false."),
		}),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.importResume]: {
		title: "Import Resume",
		description: [
			"Create a new resume from a full ResumeData JSON object (e.g. an exported file from Reactive Resume).",
			"A random name and slug are assigned automatically, like the web importer.",
			`For small edits to an existing resume, prefer \`${T.patchResume}\` instead of re-importing.`,
			"Large payloads may exceed MCP client message limits — in that case, use the web UI or the HTTP API.",
		].join("\n"),
		inputSchema: z.object({
			data: z
				.unknown()
				.describe("Complete ResumeData JSON (same shape as `read_resume` body or `resume://_meta/schema`)."),
		}),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.duplicateResume]: {
		title: "Duplicate Resume",
		description: [
			"Create a copy of an existing resume with all its data.",
			"",
			"Returns the ID of the newly duplicated resume.",
			"You must provide a new name and slug for the copy.",
			"Useful for creating job-specific variants of a base resume.",
		].join("\n"),
		inputSchema: z.object({
			id: resumeIdSchema.describe("ID of the resume to duplicate"),
			name: z.string().min(1).max(64).describe("Name for the duplicate"),
			slug: z.string().min(1).max(64).describe("URL-friendly slug for the duplicate (must be unique)"),
			tags: z.array(z.string()).optional().default([]).describe("Tags for the duplicate"),
		}),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.patchResume]: {
		title: "Apply Resume Patch",
		description: [
			"Apply JSON Patch (RFC 6902) operations to partially update a resume's data.",
			"",
			`This is the primary way to edit resume content. Use \`${T.getResume}\` first to inspect the`,
			"current structure, and `resume://_meta/schema` to understand valid paths and types.",
			"",
			"Supported operations: add, remove, replace, move, copy, test.",
			"",
			"Common path examples:",
			"  /basics/name                          — Change the name",
			"  /basics/headline                      — Change the headline",
			"  /summary/content                      — Replace summary (HTML string)",
			"  /sections/experience/items/-           — Append a new experience item",
			"  /sections/experience/items/0/company   — Update first experience's company",
			"  /sections/skills/items/-               — Append a new skill",
			"  /metadata/template                     — Change the template (e.g. 'azurill', 'bronzor', 'onyx')",
			"  /metadata/design/colors/primary        — Change the primary color (rgba string)",
			"  /sections/interests/hidden              — Hide/show a section",
			"",
			"Important: HTML content fields (description, summary.content) must use valid HTML.",
			"New items must include a valid UUID as `id` and `hidden: false`.",
			`Locked resumes cannot be patched — use \`${T.unlockResume}\` first.`,
		].join("\n"),
		inputSchema: z.object({
			id: resumeIdSchema,
			operations: resumePatchOperationsSchema,
		}),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.updateResume]: {
		title: "Update Resume (metadata)",
		description: [
			"Update resume metadata only: display name, URL slug, tags, and/or public visibility.",
			"Does not change section content — use JSON Patch via the patch tool for body edits.",
			`Locked resumes cannot be updated; use \`${T.unlockResume}\` first.`,
			"Password protection cannot be set or removed via MCP; use the web app for that.",
			"",
			"Always returns your canonical share URL (`{app}/{username}/{slug}`). Anonymous viewers can use it only when `isPublic` is true; password protection from the web app still applies.",
		].join("\n"),
		inputSchema: z.object({
			id: resumeIdSchema,
			name: z.string().min(1).max(64).optional().describe("Display name for the resume."),
			slug: z.string().min(1).max(64).optional().describe("URL-friendly slug; must stay unique among your resumes."),
			tags: z.array(z.string()).optional().describe("Replace the resume's tags (omit to leave unchanged)."),
			isPublic: z
				.boolean()
				.optional()
				.describe(
					"When true, anyone with the link can view the public resume (subject to password if set in the app).",
				),
		}),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.deleteResume]: {
		title: "Delete Resume",
		description: [
			"Permanently delete a resume and all its associated files (screenshots, PDFs).",
			"",
			`This action is IRREVERSIBLE. Locked resumes cannot be deleted — use \`${T.unlockResume}\` first.`,
			`Consider using \`${T.duplicateResume}\` to create a backup before deleting.`,
		].join("\n"),
		inputSchema: z.object({ id: resumeIdSchema }),
		annotations: WRITE_DESTRUCTIVE,
	},
	[T.lockResume]: {
		title: "Lock Resume",
		description: [
			"Lock a resume to prevent any modifications.",
			"",
			`When locked, a resume cannot be edited (${T.patchResume}, ${T.updateResume}), or deleted.`,
			"Useful for protecting finalized resumes from accidental changes.",
			`Use \`${T.unlockResume}\` to re-enable editing.`,
		].join("\n"),
		inputSchema: z.object({ id: resumeIdSchema }),
		annotations: WRITE_IDEMPOTENT,
	},
	[T.unlockResume]: {
		title: "Unlock Resume",
		description: "Unlock a previously locked resume, re-enabling edits, patches, and deletion.",
		inputSchema: z.object({ id: resumeIdSchema }),
		annotations: WRITE_IDEMPOTENT,
	},
	[T.getResumeStatistics]: {
		title: "Get Resume Statistics",
		description: [
			"Get view and download statistics for a resume.",
			"",
			"Returns: isPublic (boolean), views (count), downloads (count),",
			"lastViewedAt (timestamp or null), lastDownloadedAt (timestamp or null).",
		].join("\n"),
		inputSchema: z.object({ id: resumeIdSchema }),
		annotations: READ_IDEMPOTENT,
	},
	[T.listApplications]: {
		title: "List Applications",
		description:
			"List job applications for the authenticated account. Use this before reading or updating existing applications.",
		inputSchema: z.object({
			status: applicationStatusSchema.optional(),
			tags: z.array(z.string()).optional().default([]),
			includeArchived: z.boolean().optional().default(false),
		}),
		annotations: READ_IDEMPOTENT,
	},
	[T.readApplication]: {
		title: "Read Application",
		description: "Read one full job application, including contacts, document URLs, follow-up details, and timeline.",
		inputSchema: z.object({ id: applicationIdSchema }),
		annotations: READ_IDEMPOTENT,
	},
	[T.listApplicationTags]: {
		title: "List Application Tags",
		description: "Return every distinct tag used across job applications.",
		inputSchema: z.object({}),
		annotations: READ_IDEMPOTENT,
	},
	[T.getApplicationStats]: {
		title: "Get Application Stats",
		description: "Return aggregate application counts by pipeline stage and source for insights.",
		inputSchema: z.object({}),
		annotations: READ_IDEMPOTENT,
	},
	[T.createApplication]: {
		title: "Create Application",
		description: "Create a tracked job application. Company and role are required.",
		inputSchema: createApplicationSchema,
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.updateApplication]: {
		title: "Update Application",
		description:
			"Update application fields, move stages, archive/unarchive, edit contacts, follow-up, tags, or linked resume.",
		inputSchema: z.object({
			id: applicationIdSchema,
			...applicationMutableFieldsSchema,
			archived: z.boolean().optional().describe("Whether the application is hidden from active views."),
		}),
		annotations: WRITE_IDEMPOTENT,
	},
	[T.addApplicationNote]: {
		title: "Add Application Note",
		description: "Append a free-text note to an application's timeline.",
		inputSchema: z.object({
			id: applicationIdSchema,
			text: z.string().min(1),
			date: timelineDateSchema.optional().describe("Optional note date in YYYY-MM-DD format."),
		}),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.updateApplicationTimelineEntry]: {
		title: "Update Application Timeline Entry",
		description: "Update a timeline entry date, or note text for note entries.",
		inputSchema: z
			.object({
				id: applicationIdSchema,
				entryId: applicationTimelineEntryIdSchema,
				date: timelineDateSchema.optional().describe("Replacement row date in YYYY-MM-DD format."),
				text: z.string().min(1).optional().describe("Replacement note text. Only note entries can change text."),
			})
			.refine((value) => value.date !== undefined || value.text !== undefined, "Provide date or text to update."),
		annotations: WRITE_IDEMPOTENT,
	},
	[T.deleteApplicationTimelineEntry]: {
		title: "Delete Application Timeline Entry",
		description: "Delete a note or older stage entry. The current stage entry cannot be deleted.",
		inputSchema: z.object({ id: applicationIdSchema, entryId: applicationTimelineEntryIdSchema }),
		annotations: WRITE_DESTRUCTIVE,
	},
	[T.deleteApplication]: {
		title: "Delete Application",
		description: "Permanently delete one job application and its owned uploaded documents.",
		inputSchema: z.object({ id: applicationIdSchema }),
		annotations: WRITE_DESTRUCTIVE,
	},
	[T.bulkUpdateApplications]: {
		title: "Bulk Update Applications",
		description: "Move, archive/unarchive, or add tags to multiple applications.",
		inputSchema: z.object({
			ids: z.array(z.string()).min(1),
			status: applicationStatusSchema.optional(),
			archived: z.boolean().optional(),
			addTags: z.array(z.string()).optional(),
		}),
		annotations: WRITE_IDEMPOTENT,
	},
	[T.bulkDeleteApplications]: {
		title: "Bulk Delete Applications",
		description: "Permanently delete multiple applications.",
		inputSchema: z.object({ ids: z.array(z.string()).min(1) }),
		annotations: WRITE_DESTRUCTIVE,
	},
	[T.importApplications]: {
		title: "Import Applications",
		description: "Bulk-create application rows parsed from CSV or another source. Maximum 500 items.",
		inputSchema: z.object({ items: z.array(createApplicationSchema).min(1).max(500) }),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.attachApplicationDocument]: {
		title: "Attach Application Document",
		description: "Attach a sent resume or cover-letter PDF to an application using base64-encoded PDF bytes.",
		inputSchema: z.object({
			id: applicationIdSchema,
			kind: applicationDocumentKindSchema,
			fileName: z.string().min(1),
			contentType: z.literal("application/pdf"),
			dataBase64: pdfBase64Schema,
		}),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.removeApplicationDocument]: {
		title: "Remove Application Document",
		description: "Remove a sent resume or cover-letter PDF from an application.",
		inputSchema: z.object({ id: applicationIdSchema, kind: applicationDocumentKindSchema }),
		annotations: WRITE_IDEMPOTENT,
	},
	[T.autofillApplicationFromJob]: {
		title: "Autofill Application From Job",
		description:
			"Use AI to extract company, role, location, salary, and job description from a job URL or pasted posting.",
		inputSchema: z.object({
			sourceUrl: httpUrlSchema.optional(),
			jobDescription: z.string().max(20_000).optional(),
		}),
		annotations: READ_OPEN_WORLD_NON_IDEMPOTENT,
	},
	[T.scoreApplicationMatch]: {
		title: "Score Application Match",
		description: "Score the linked resume against the application's job description and persist match metadata.",
		inputSchema: z.object({ id: applicationIdSchema }),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.tailorResumeForApplication]: {
		title: "Tailor Resume For Application",
		description: "Create and link a tailored copy of the application's linked resume.",
		inputSchema: z.object({ id: applicationIdSchema }),
		annotations: WRITE_NON_IDEMPOTENT,
	},
	[T.draftApplicationMessage]: {
		title: "Draft Application Message",
		description: "Draft either a cover letter or recruiter follow-up from application and resume context.",
		inputSchema: z.object({ id: applicationIdSchema, kind: z.enum(["cover-letter", "follow-up"]) }),
		annotations: READ_NON_IDEMPOTENT,
	},
} as const;
