import { z } from 'zod';
import { t } from '../../i18n/index.js';
import { safeTracked, safeLicensed } from '../safe-handler.js';
import { SupportedLocaleEnum, SpecStatusEnum, SpecTypeEnum, SpecScopeEnum, SpecTargetEnum, ExperienceLevelEnum, WorkModeEnum, ListSpecsOutputSchema, EstimateOutputSchema, ValidateOutputSchema, SpecIdSchema, } from '../schemas/index.js';
import { handleSetLocale } from '../set-locale.js';
import { handleInitProject } from '../init-project.js';
import { handleSetWorkMode } from '../set-work-mode-handler.js';
import { handleInitConstitution } from '../init-constitution.js';
import { handleClarifyRequirements } from '../clarify-requirements.js';
import { handleCreateSpec } from '../create-spec.js';
import { handleListSpecs } from '../list-specs.js';
import { handleUpdateStatus } from '../update-status.js';
import { handleUpdateStatusBatch } from '../update-status/batch.js';
import { handleEstimate } from '../estimate.js';
import { handleReverseEngineer } from '../reverse-engineer.js';
import { handleValidate } from '../validate.js';
import { handleTypeSafetyGate } from '../type-safety-gate.js';
/** init_project inputSchema — extracted to keep registerCoreSpecTools within line budget. */
const INIT_PROJECT_INPUT_SCHEMA = {
    projectPath: z.string().max(4096).describe('Absolute path to the project root'),
    mode: z
        .enum(['existing_project', 'new_project'])
        .optional()
        .describe('Onboarding mode. Defaults to existing_project for backwards compatibility.'),
    parentWorkspacePath: z
        .string()
        .max(4096)
        .optional()
        .describe('Parent workspace path for explicit new_project onboarding'),
    appName: z.string().max(500).optional().describe('Human-readable app name for new_project'),
    appSlug: z
        .string()
        .max(200)
        .optional()
        .describe('Safe folder slug for new_project; must not contain path separators'),
    projectType: z.string().max(500).optional().describe('Type of project being initialized'),
    platform: z.string().max(500).optional().describe('Runtime/deployment platform'),
    language: z.string().max(500).optional().describe('User-confirmed language'),
    framework: z.string().max(500).nullable().optional().describe('User-confirmed framework'),
    database: z.string().max(500).nullable().optional().describe('User-confirmed database'),
    createDirectory: z
        .boolean()
        .optional()
        .describe('When true with mode=new_project, create only the resolved projectPath directory'),
    technologySelectionContract: z
        .record(z.string(), z.unknown())
        .optional()
        .describe('Approved TechnologySelectionContract returned by facilitate new_project flow'),
    locale: SupportedLocaleEnum.optional().describe('Preferred locale for this project'),
    hourlyRate: z.number().optional().describe('Developer hourly rate in USD for cost estimation'),
    experienceLevel: ExperienceLevelEnum.optional().describe('Developer experience level: beginner, intermediate, or expert'),
    userProfile: z
        .enum(['developer', 'product-owner', 'designer', 'non-technical'])
        .optional()
        .describe('User profile type — determines how detailed and technical Planu interactions will be. Ask the user before running init_project.'),
    autoInstallSkills: z
        .boolean()
        .optional()
        .describe('Auto-install recommended skills into .claude/skills/ during init (default: true). Set to false to only list recommendations without installing.'),
    workMode: WorkModeEnum.optional().describe('Preferred interaction style: guided (step-by-step hints for SDD newcomers) | standard (normal workflow, default) | expert (concise output, no onboarding hints). Ask the user before running init_project.'),
    clarificationAnswers: z
        .record(z.string(), z.string())
        .optional()
        .describe('SPEC-584: Answers to pending interactiveQuestions from the previous call. ' +
        'Key = question header/key, value = selected option label. ' +
        'REQUIRED if the previous response contained interactiveQuestions[]. ' +
        'Omitting this field when a pending token exists returns isError: true.'),
    permissionsMode: z
        .enum(['minimal', 'recommended', 'full'])
        .optional()
        .describe('SPEC-594: Opt-in — configure .claude/settings.json permissions during init. ' +
        'minimal=only mcp__planu__* + deny rules; ' +
        'recommended=above + dev tooling + git + FS reads; ' +
        'full=above + Bash(*) blanket.'),
    pluginsMode: z
        .enum(['minimal', 'recommended', 'full'])
        .optional()
        .describe('SPEC-596: Opt-in — register Anthropic marketplace + install plugins during init. ' +
        'minimal=only always-tier plugins (commit-commands); ' +
        'recommended=above + stack-matched plugins; ' +
        'full=all catalog plugins for detected stacks.'),
};
/**
 * Registers core spec lifecycle tools (tools 1–10) on the MCP server.
 */
// eslint-disable-next-line max-lines-per-function -- tool registrar: each block registers one discrete MCP tool; extraction would not reduce complexity
export function registerCoreSpecTools(server) {
    // 1. set_locale
    server.registerTool('set_locale', {
        description: t('tools.set_locale.description'),
        inputSchema: {
            locale: SupportedLocaleEnum.describe('Target locale (en, es, pt)'),
            projectId: z
                .string()
                .max(500)
                .optional()
                .describe('Optional project ID to set locale per-project'),
        },
    }, safeTracked('set_locale', async (args) => handleSetLocale(args)));
    // 2. init_project
    server.registerTool('init_project', {
        description: t('tools.init_project.description'),
        inputSchema: INIT_PROJECT_INPUT_SCHEMA,
    }, safeTracked('init_project', async (args) => handleInitProject(args)));
    // 2b. set_work_mode — SPEC-313
    server.registerTool('set_work_mode', {
        description: 'Change your preferred interaction style with Planu. Stored in planu.json. ' +
            'guided = step-by-step hints for SDD newcomers. ' +
            'standard = normal workflow (default). ' +
            'expert = concise output, no onboarding hints.',
        inputSchema: {
            projectPath: z.string().max(4096).describe('Absolute path to the project root'),
            workMode: WorkModeEnum.describe('New work mode: guided | standard | expert'),
        },
        annotations: { readOnlyHint: false },
    }, safeTracked('set_work_mode', async (args) => handleSetWorkMode(args)));
    // 3. init_constitution
    server.registerTool('init_constitution', {
        description: t('tools.init_constitution.description'),
        inputSchema: {
            projectId: z.string().max(500).describe('Project ID'),
            autoDetect: z
                .boolean()
                .optional()
                .describe('Auto-detect principles from codebase (default: true)'),
            principles: z
                .array(z.object({
                id: z.string().max(500),
                category: z
                    .enum([
                    'architecture',
                    'security',
                    'quality',
                    'conventions',
                    'tech-stack',
                    'workflow',
                ])
                    .describe('Principle category: architecture | security | quality | conventions | tech-stack | workflow'),
                principle: z.string().max(10_000),
                rationale: z.string().max(10_000),
                enforceLevel: z
                    .enum(['strict', 'recommended'])
                    .describe('Enforcement level: strict | recommended'),
                autoEnforceable: z.boolean(),
                source: z
                    .enum(['auto-detected', 'user-defined', 'claude-md', 'learned'])
                    .describe('Origin of this principle: auto-detected | user-defined | claude-md | learned'),
                immutable: z.boolean(),
            }))
                .max(1000)
                .optional()
                .describe('Manual principles to include'),
        },
    }, safeTracked('init_constitution', async (args) => handleInitConstitution(args)));
    // 4. clarify_requirements
    server.registerTool('clarify_requirements', {
        description: t('tools.clarify_requirements.description'),
        inputSchema: {
            projectId: z.string().max(500).describe('Project ID'),
            topic: z.string().max(500).describe('The topic or feature to clarify'),
            context: z
                .string()
                .max(10_000)
                .optional()
                .describe('Additional context about the requirement'),
            maxQuestions: z
                .number()
                .optional()
                .describe('Maximum number of questions to generate (default: 5)'),
        },
    }, safeTracked('clarify_requirements', async (args) => handleClarifyRequirements(args)));
    // 5. create_spec
    server.registerTool('create_spec', {
        description: t('tools.create_spec.description'),
        inputSchema: {
            title: z.string().min(1).max(500).describe('Title of the spec / user story'),
            description: z
                .string()
                .max(10_000)
                .describe('Detailed description of the feature or change. ' +
                'Max 10000 chars. For larger specs, create with a summary description (<3k chars) ' +
                'and append the rest via reconcile_spec post-creation.'),
            projectPath: z
                .string()
                .max(4096)
                .optional()
                .describe('Absolute path to the project root. Auto-detected from git root when omitted.'),
            type: SpecTypeEnum.optional().describe('Spec type (default: feature)'),
            scope: SpecScopeEnum.optional().describe('Spec scope (default: feature)'),
            target: SpecTargetEnum.optional().describe('Target area: frontend, backend, shared, fullstack, infrastructure, database'),
            tags: z.array(z.string().max(500)).max(100).optional().describe('Tags for categorization'),
            feature: z
                .string()
                .max(500)
                .optional()
                .describe('Feature group tag (e.g. "Authentication", "Billing"). Added to tags for categorization. Flat structure — no subdirectories created.'),
            acFormat: z
                .enum(['bdd', 'checkbox'])
                .optional()
                .describe('SPEC-224: Format for acceptance criteria in spec.md. ' +
                'checkbox (default) — markdown checkboxes (- [ ] criterion). ' +
                'bdd — Gherkin Given-When-Then scenarios for BDD workflows.'),
            clarificationAnswers: z
                .record(z.string(), z.string())
                .optional()
                .describe('SPEC-584: Answers to pending interactiveQuestions from the previous call. ' +
                'Key = question header/key, value = selected option label. ' +
                'REQUIRED if the previous response contained interactiveQuestions[]. ' +
                'Omitting this field when a pending token exists returns isError: true.'),
            outOfScope: z
                .array(z.string())
                .optional()
                .describe('SPEC-612: Explicit out-of-scope items for this spec. If omitted, Planu auto-suggests based on the description.'),
            specId: z
                .string()
                .optional()
                .describe('SPEC-697: Spec ID for agent team findings synthesis. Required when agentTeamFindings is provided.'),
            agentTeamFindings: z
                .array(z.object({
                role: z
                    .string()
                    .describe('Role identifier matching one of the agentTeamPlan roles (e.g. "react-expert")'),
                findings: z.string().describe('Markdown findings from the agent. Max 300 words.'),
            }))
                .optional()
                .describe('SPEC-697: Agent team findings from parallel domain-expert agents. When present with specId, Planu synthesizes these into the ## Technical section of spec.md.'),
            sessionId: z
                .string()
                .optional()
                .describe('[SPEC-722] Session ID of the current LLM session. Used to issue a planner token ' +
                'that a reviewer session can use to verify independent identity.'),
            modelId: z
                .string()
                .optional()
                .describe('[SPEC-722] Model ID of the planner (e.g. "claude-opus-4-5").'),
            host: z
                .string()
                .optional()
                .describe('[SPEC-722] Host/agent platform identifier (e.g. "claude-code", "cursor"). ' +
                'Embedded in the planner token for reviewer differentiation.'),
        },
    }, safeTracked('create_spec', async (args) => handleCreateSpec(args, server)));
    // 6. list_specs
    server.registerTool('list_specs', {
        description: t('tools.list_specs.description'),
        inputSchema: {
            projectId: z
                .string()
                .max(500)
                .optional()
                .describe('Project ID (hash). Prefer projectPath if unknown.'),
            projectPath: z
                .string()
                .max(4096)
                .optional()
                .describe('Absolute path to the project root. Used to derive projectId automatically.'),
            status: SpecStatusEnum.optional().describe('Filter by spec status'),
            type: SpecTypeEnum.optional().describe('Filter by spec type'),
            detail: z
                .enum(['summary', 'full'])
                .optional()
                .describe('Response detail level: summary (default, returns id/title/status/type/tags as markdown table) | full (returns all fields as JSON)'),
            clarificationAnswers: z
                .record(z.string(), z.string())
                .optional()
                .describe('SPEC-584: Answers to pending interactiveQuestions from the previous call. ' +
                'Key = question header/key, value = selected option label. ' +
                'REQUIRED if the previous response contained interactiveQuestions[]. ' +
                'Omitting this field when a pending token exists returns isError: true.'),
        },
        outputSchema: ListSpecsOutputSchema,
    }, safeTracked('list_specs', async (args) => handleListSpecs(args)));
    // 7. update_status
    server.registerTool('update_status', {
        description: t('tools.update_status.description'),
        inputSchema: {
            specId: SpecIdSchema.describe('Spec ID to update'),
            projectId: z
                .string()
                .max(500)
                .optional()
                .describe('Project ID (hash). Prefer projectPath if unknown.'),
            projectPath: z
                .string()
                .max(4096)
                .optional()
                .describe('Absolute path to the project root. Used to derive projectId automatically.'),
            status: SpecStatusEnum.describe('New status'),
            sessionId: z
                .string()
                .max(500)
                .optional()
                .describe('Agent/session ID performing this lifecycle transition.'),
            modelId: z
                .string()
                .max(500)
                .optional()
                .describe('Concrete model ID used for this transition. Can satisfy SDD model-routing evidence when it maps to the required tier.'),
            modelTierUsed: z
                .enum(['max', 'implementation', 'review'])
                .optional()
                .describe('SDD model tier evidence for this phase: max for approval/arbitration, implementation for coding, review for reviewer work.'),
            contextHash: z
                .string()
                .max(500)
                .optional()
                .describe('SHA-256 hash of the persisted context package used to prove context continuity across agents.'),
            handoffPath: z
                .string()
                .max(4096)
                .optional()
                .describe('Path to the persisted handoff package generated by package_handoff. Required for implementing/done unless handoffArtifactId is provided.'),
            handoffArtifactId: z
                .string()
                .max(500)
                .optional()
                .describe('External handoff artifact ID when the host stores handoff evidence outside the filesystem.'),
            reviewedBy: z
                .string()
                .max(500)
                .optional()
                .describe('Reviewer identity/evidence required before status=done.'),
            arbitratedBy: z
                .string()
                .max(500)
                .optional()
                .describe('Arbiter identity/evidence required before status=done.'),
            reconcileRequired: z
                .boolean()
                .optional()
                .describe('Set true when implementation drift requires reconcile_spec before status=done; true blocks done.'),
            actuals: z
                .object({
                devHours: z.number().min(0),
                reviewHours: z.number().min(0),
                tokensOpus: z.number().min(0),
                tokensSonnet: z.number().min(0),
                apiCostUsd: z.number().min(0),
                humanCostUsd: z.number().min(0),
                totalCostUsd: z.number().min(0),
                completedAt: z.string().max(500),
                notes: z.string().max(10_000),
            })
                .optional()
                .describe('Actual metrics (required when status = done)'),
            reviewNotes: z
                .string()
                .max(10_000)
                .optional()
                .describe('Feedback or observations when sending a spec back (e.g., review→draft). Stored in the spec for the implementer to address.'),
            force: z
                .boolean()
                .optional()
                .describe('Force transition to done bypassing DoD gates. Use when validate already confirmed the spec is ready but auto-check gates cannot be satisfied (e.g., no test files detected, manual review pending).'),
            forceStatus: z
                .boolean()
                .optional()
                .describe('Bypass auto-validate score check when marking a spec as done. Use when validate score is below 70 but you have confirmed readiness manually.'),
            forceStatusReason: z
                .string()
                .max(2000)
                .optional()
                .describe('SPEC-721: Required when forceStatus=true. ≥100 characters explaining why validate gate is bypassed.'),
            dry_run: z
                .boolean()
                .optional()
                .describe('SPEC-731: Non-destructive preview mode. Runs all gates but does NOT write to disk, append to transition-log, or acquire any lock. Returns the same shape as a real call with dryRun: true discriminator.'),
            reason: z
                .string()
                .max(2000)
                .optional()
                .describe('SPEC-733: Required for reverse transitions (done→implementing, discarded→draft). Must be ≥30 chars explaining why the spec is being reopened.'),
            forceApprove: z
                .boolean()
                .optional()
                .describe('SPEC-769: Force-approve a spec that scored below 70 on the readiness gate. Warnings are stored in spec.md frontmatter. Use when the LLM has explicitly confirmed the spec is ready despite a low readiness score.'),
        },
    }, safeTracked('update_status', async (args) => handleUpdateStatus(args)));
    server.registerTool('update_status_batch', {
        description: 'Batch update many specs to the same status in one MCP execution. Uses the same transition rules as update_status and reports updated/skipped/failed per spec.',
        inputSchema: {
            specIds: z.array(SpecIdSchema).min(1).describe('Spec IDs to update'),
            projectId: z.string().max(500).optional().describe('Project ID, if known'),
            projectPath: z
                .string()
                .max(4096)
                .optional()
                .describe('Absolute project root. Preferred when projectId is unknown.'),
            status: SpecStatusEnum.describe('New status for all specs'),
            dryRun: z.boolean().optional().describe('Preview the batch without mutating any spec.'),
            reviewNotes: z
                .string()
                .max(10_000)
                .optional()
                .describe('Optional notes for each transition.'),
        },
    }, safeTracked('update_status_batch', async (args) => handleUpdateStatusBatch(args)));
    // 8. estimate
    server.registerTool('estimate', {
        description: t('tools.estimate.description'),
        inputSchema: {
            specId: SpecIdSchema.describe('Spec ID to estimate'),
            projectId: z.string().max(500).describe('Project ID'),
        },
        outputSchema: EstimateOutputSchema,
    }, safeTracked('estimate', async (args) => handleEstimate(args)));
    // 9. reverse_engineer
    server.registerTool('reverse_engineer', {
        description: t('tools.reverse_engineer.description'),
        inputSchema: {
            path: z.string().max(4096).describe('Path to the file or directory to analyze'),
            projectId: z.string().max(500).describe('Project ID'),
            depth: z
                .enum(['shallow', 'deep', 'deep-v2'])
                .optional()
                .describe('Analysis depth: shallow (fast scan), deep (full file scan), deep-v2 (full scan + test coverage, dependency graph, API detection, complexity metrics, config analysis). Default: shallow'),
        },
    }, safeLicensed('reverse_engineer', async (args) => handleReverseEngineer(args)));
    // 10. validate
    server.registerTool('validate', {
        description: t('tools.validate.description'),
        inputSchema: {
            specId: SpecIdSchema.describe('Spec ID to validate'),
            projectId: z
                .string()
                .max(500)
                .optional()
                .describe('Project ID hash. Prefer projectPath — stays correct after context compaction.'),
            projectPath: z
                .string()
                .max(4096)
                .optional()
                .describe('Absolute path to project root. Derives projectId automatically — always correct even after context compaction.'),
        },
        outputSchema: ValidateOutputSchema,
    }, safeTracked('validate', async (args) => handleValidate(args)));
    // 10b. type_safety_gate — SPEC-968
    server.registerTool('type_safety_gate', {
        description: 'Gate that blocks update_status(done) if new code contains TypeScript anti-patterns. ' +
            'Scans changed TypeScript files for `any` types, unjustified `@ts-ignore` / `@ts-expect-error`, ' +
            'and missing parameter type annotations. Returns a health score (0-100) and actionable suggestions.',
        inputSchema: {
            projectPath: z.string().max(4096).describe('Absolute path to the project root'),
            specId: SpecIdSchema.describe('Spec ID being validated'),
            waiver: z
                .array(z.string().max(4096))
                .max(100)
                .optional()
                .describe('File paths (relative to project root) to exclude from scanning'),
        },
    }, safeTracked('type_safety_gate', (args) => Promise.resolve(handleTypeSafetyGate({
        projectPath: args.projectPath,
        specId: args.specId,
        waiver: args.waiver ?? undefined,
    }))));
}
//# sourceMappingURL=core-spec-tools.js.map