import { z } from 'zod';
import { getGitMeta } from '../git_meta.js';
import { findSkills, getSkillBody, getSkillResource } from '../skills.js';
import type { ToolDef } from './types.js';

// v1.8 Level 1 branch awareness: gives the model a read-only view of the
// project's git state. No path input — operates on the inference-resolved
// project root via getGitMeta. Subprocess runs with a 2s timeout (see git_meta).
const GitStatusInput = z.object({}).strict();
type GitStatusInputT = z.infer<typeof GitStatusInput>;

export const gitStatus: ToolDef<GitStatusInputT> = {
  name: 'git_status',
  description:
    "Returns the current git branch, whether the working tree is dirty, and ahead/behind counts vs upstream. Read-only. Use when you need to know which branch the user is currently working on.",
  inputSchema: GitStatusInput,
  jsonSchema: {
    type: 'function',
    function: {
      name: 'git_status',
      description:
        'Returns the current git branch, dirty flag, and ahead/behind counts vs upstream. Read-only.',
      parameters: {
        type: 'object',
        properties: {},
        additionalProperties: false,
      },
    },
  },
  async execute(_input, projectRoot) {
    const meta = await getGitMeta(projectRoot);
    if (meta === null) {
      return { repo: false, branch: null, is_dirty: false, ahead: 0, behind: 0 };
    }
    return { repo: true, ...meta };
  },
};

// Batch 9.6: skill_find, skill_use, skill_resource. Lazy-loaded markdown
// playbooks at /data/skills/. Three tools rather than one to keep each call
// cheap — the model lists, then loads, then optionally pulls support files.

const SkillFindInput = z.object({
  query: z.string().optional(),
});
type SkillFindInputT = z.infer<typeof SkillFindInput>;

export const skillFind: ToolDef<SkillFindInputT> = {
  name: 'skill_find',
  description:
    'Find skills (markdown playbooks under /data/skills) by name or description. Returns up to 5 matches. Empty query or "*" returns all available skills. Call this first to discover what skills are available.',
  inputSchema: SkillFindInput,
  jsonSchema: {
    type: 'function',
    function: {
      name: 'skill_find',
      description:
        'Find skills by name or description. Returns up to 5 matches. Empty or "*" returns all.',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string', description: 'substring matched against skill name and description' },
        },
        additionalProperties: false,
      },
    },
  },
  async execute(input) {
    return await findSkills(input.query ?? '');
  },
};

const SkillUseInput = z.object({
  name: z.string().min(1),
});
type SkillUseInputT = z.infer<typeof SkillUseInput>;

export const skillUse: ToolDef<SkillUseInputT> = {
  name: 'skill_use',
  description:
    "Load the full body of a skill's SKILL.md by name. Returns the markdown playbook to follow. Discover names via skill_find. Errors: unknown_skill.",
  inputSchema: SkillUseInput,
  jsonSchema: {
    type: 'function',
    function: {
      name: 'skill_use',
      description: "Load the full body of a skill's SKILL.md by name.",
      parameters: {
        type: 'object',
        properties: {
          name: { type: 'string', description: 'skill name from skill_find' },
        },
        required: ['name'],
        additionalProperties: false,
      },
    },
  },
  async execute(input) {
    const body = await getSkillBody(input.name);
    if (body === null) {
      return { error: 'unknown_skill', message: `unknown skill: ${input.name}` };
    }
    return { body };
  },
};

const SkillResourceInput = z.object({
  name: z.string().min(1),
  path: z.string().min(1),
});
type SkillResourceInputT = z.infer<typeof SkillResourceInput>;

export const skillResource: ToolDef<SkillResourceInputT> = {
  name: 'skill_resource',
  description:
    "Read a support file inside a skill's folder (e.g. references/root-cause-tracing.md). Path is relative to the skill folder. Use skill_use to read SKILL.md itself. Errors: unknown_skill, unknown_resource, path_escape.",
  inputSchema: SkillResourceInput,
  jsonSchema: {
    type: 'function',
    function: {
      name: 'skill_resource',
      description: "Read a support file inside a skill's folder. Path is relative to the skill folder.",
      parameters: {
        type: 'object',
        properties: {
          name: { type: 'string', description: 'skill name' },
          path: { type: 'string', description: 'relative path under the skill folder' },
        },
        required: ['name', 'path'],
        additionalProperties: false,
      },
    },
  },
  async execute(input) {
    const result = await getSkillResource(input.name, input.path);
    if (!result.ok) {
      return { error: result.code, message: result.message };
    }
    return { content: result.content };
  },
};

// Batch 9.7: ask_user_input. Interactive elicitation. The model emits a tool
// call with 1-3 structured questions; the inference loop PAUSES (does not
// execute the tool server-side, does not recurse) and waits for the frontend
// to POST /api/chats/:id/answer_user_input with the user's selections. See
// routes/messages.ts for the resume path and services/inference.ts for the
// pause branch in executeToolPhase.
const AskUserInputInput = z.object({
  questions: z
    .array(
      z.object({
        question: z.string().min(1).max(200),
        type: z.enum(['single_select', 'multi_select']),
        options: z.array(z.string().min(1).max(80)).min(2).max(6),
      }),
    )
    .min(1)
    .max(3),
});
type AskUserInputInputT = z.infer<typeof AskUserInputInput>;

export const askUserInput: ToolDef<AskUserInputInputT> = {
  name: 'ask_user_input',
  description:
    "Ask the user 1-3 structured questions through an inline picker UI. Use when you genuinely need a choice the user must make (e.g. scope, options, preferences) before continuing. Each question has 2-6 options and accepts free-text answers in addition. The tool call pauses the conversation until the user submits — the next assistant turn sees their answers as the tool result. Do not use for trivial yes/no clarifications you could infer; prefer it over multi-paragraph speculation about what the user might want.",
  inputSchema: AskUserInputInput,
  jsonSchema: {
    type: 'function',
    function: {
      name: 'ask_user_input',
      description:
        'Ask the user 1-3 structured questions through an inline picker. Pauses the conversation until the user answers; the next turn sees their selections.',
      parameters: {
        type: 'object',
        properties: {
          questions: {
            type: 'array',
            minItems: 1,
            maxItems: 3,
            items: {
              type: 'object',
              properties: {
                question: { type: 'string', description: '<=200 chars, shown to the user' },
                type: {
                  type: 'string',
                  enum: ['single_select', 'multi_select'],
                  description: 'single_select = at most one option; multi_select = any subset',
                },
                options: {
                  type: 'array',
                  minItems: 2,
                  maxItems: 6,
                  items: { type: 'string' },
                  description: '2-6 strings, each <=80 chars; free-text input is always available alongside',
                },
              },
              required: ['question', 'type', 'options'],
              additionalProperties: false,
            },
          },
        },
        required: ['questions'],
        additionalProperties: false,
      },
    },
  },
  // Server-side no-op. The "execution" of ask_user_input is the user's
  // response, captured client-side and posted to /api/chats/:id/answer_user_input.
  // The inference loop detects this tool by name and pauses before reaching
  // executeToolCall — this fallback only runs if something bypasses that
  // branch, in which case the pending sentinel matches the pause-path shape.
  async execute(input) {
    return { _pending: true, questions: input.questions };
  },
};
