import { z } from 'zod';

const SEMVER_RE = /^\d+\.\d+\.\d+$/;
const NAME_RE = /^[a-z0-9-]+$/;

/**
 * Typed Skill metadata. Stored opaquely as `Skill.metadata` Json in the
 * database; validated app-layer when callers pass it through CreateSkill/
 * UpdateSkill. The fields below are the ones the sync command (PR-5) will
 * actually act on:
 *
 *   - `hooks`        — declarative SessionStart / PreToolUse / PostToolUse
 *                      entries that mcpctl skills sync registers in
 *                      ~/.claude/settings.json with `_mcpctl_managed: true`.
 *   - `mcpServers`   — upstream MCP server dependencies the skill needs;
 *                      sync auto-attaches them to the project (corporate
 *                      trust model — no consent prompt).
 *   - `postInstall`  — relative path inside `files{}` to a script that
 *                      sync runs as the user when the skill's contentHash
 *                      first appears or changes. 60-s default timeout;
 *                      audit event emitted back to mcpd.
 *   - `preUninstall` — symmetric to postInstall, runs on orphan removal.
 *   - `postInstallTimeoutSec` — per-skill override for the 60-s default.
 *
 * .passthrough() so unknown fields survive the round-trip — forward
 * compatibility for follow-on metadata additions.
 */

const ManagedHookEntrySchema = z.object({
  type: z.literal('command'),
  command: z.string().min(1).max(4000),
  timeout: z.number().int().min(1).max(3600).optional(),
}).passthrough();

const HooksSchema = z.object({
  PreToolUse: z.array(ManagedHookEntrySchema).optional(),
  PostToolUse: z.array(ManagedHookEntrySchema).optional(),
  SessionStart: z.array(ManagedHookEntrySchema).optional(),
  Stop: z.array(ManagedHookEntrySchema).optional(),
  SubagentStop: z.array(ManagedHookEntrySchema).optional(),
  Notification: z.array(ManagedHookEntrySchema).optional(),
}).strict().optional();

const McpServerDepSchema = z.object({
  name: z.string().regex(NAME_RE),
  fromTemplate: z.string().min(1),
  project: z.string().regex(NAME_RE).optional(),
}).strict();

export const SkillMetadataSchema = z.object({
  hooks: HooksSchema,
  mcpServers: z.array(McpServerDepSchema).optional(),
  postInstall: z.string().min(1).max(500).optional(),
  preUninstall: z.string().min(1).max(500).optional(),
  postInstallTimeoutSec: z.number().int().min(1).max(600).optional(),
}).passthrough();

export const CreateSkillSchema = z
  .object({
    name: z.string().min(1).max(100).regex(NAME_RE, 'Name must be lowercase alphanumeric with hyphens'),
    content: z.string().min(1).max(200_000),
    description: z.string().max(500).optional(),
    files: z.record(z.string()).optional(),
    metadata: SkillMetadataSchema.optional(),
    projectId: z.string().optional(),
    agentId: z.string().optional(),
    priority: z.number().int().min(1).max(10).default(5).optional(),
    semver: z.string().regex(SEMVER_RE, 'Semver must be MAJOR.MINOR.PATCH').optional(),
  })
  .refine(
    (data) => !(data.projectId !== undefined && data.agentId !== undefined),
    { message: 'A skill may attach to a project XOR an agent, not both', path: ['agentId'] },
  );

export const UpdateSkillSchema = z.object({
  content: z.string().min(1).max(200_000).optional(),
  description: z.string().max(500).optional(),
  files: z.record(z.string()).optional(),
  metadata: SkillMetadataSchema.optional(),
  priority: z.number().int().min(1).max(10).optional(),
  semver: z.string().regex(SEMVER_RE, 'Semver must be MAJOR.MINOR.PATCH').optional(),
  bump: z.enum(['major', 'minor', 'patch']).optional(),
  note: z.string().max(500).optional(),
});

export type CreateSkillInput = z.infer<typeof CreateSkillSchema>;
export type UpdateSkillInput = z.infer<typeof UpdateSkillSchema>;
export type SkillMetadata = z.infer<typeof SkillMetadataSchema>;
