'use strict';

const path = require('node:path');
const skillAutocomplete = require('./skill-autocomplete');

const SUPPORTED_INTENTS = Object.freeze({
  finish_and_merge: true,
  commit_main: true,
  create_pr: true,
});

const MESSAGE_ONLY_REASON = 'message_only';
let intentCache = new Map();

function normalizeIntent(value) {
  const text = String(value || '').trim().toLowerCase().replace(/[-\s]+/g, '_');
  if (text === 'finish' || text === 'finish_merge' || text === 'merge_main') return 'finish_and_merge';
  if (text === 'commit' || text === 'commit_local' || text === 'main_commit') return 'commit_main';
  if (text === 'pr' || text === 'pull_request' || text === 'create_pull_request') return 'create_pr';
  return SUPPORTED_INTENTS[text] ? text : '';
}

function resolveSkillIntent(options = {}) {
  const intent = normalizeIntent(options.intent);
  if (!intent) {
    return {
      ok: false,
      error: 'unsupported_intent',
      intent: String(options.intent || ''),
      skill: null,
      candidates: [],
    };
  }

  const agent = skillAutocomplete.normalizeSkillAgentType(options.agent) || 'all';
  const cwd = normalizeCwd(options.cwd);
  const cacheKey = `${cwd}\0${agent}\0${intent}`;
  const cached = intentCache.get(cacheKey);
  if (cached) return cloneResult({ ...cached, cached: true });

  const raw = skillAutocomplete.loadAllSkillEntries(cwd || undefined);
  const compatible = raw
    .filter((skill) => agent === 'all' || skillAutocomplete.isSkillCompatibleWithAgent(skill, agent))
    .sort((a, b) => {
      const sourceDelta = skillAutocomplete.skillSourcePriorityForAgent(a.source, agent) -
        skillAutocomplete.skillSourcePriorityForAgent(b.source, agent);
      if (sourceDelta) return sourceDelta;
      return String(a.name || '').localeCompare(String(b.name || ''), undefined, {
        numeric: true,
        sensitivity: 'base',
      });
    });

  const result = resolveSkillIntentFromSkills(compatible, { intent, agent, cwd });
  intentCache.set(cacheKey, cloneResult({ ...result, cached: false }));
  return cloneResult(result);
}

function resolveSkillIntentFromSkills(skills, options = {}) {
  const intent = normalizeIntent(options.intent);
  const agent = skillAutocomplete.normalizeSkillAgentType(options.agent) || 'all';
  const assessed = safeArray(skills)
    .map((skill, index) => assessSkillForIntent(skill, { intent, agent, index }))
    .sort(compareIntentCandidates);
  const candidates = assessed.filter((candidate) => candidate.score > 0 || candidate.eligible || candidate.negative.length > 0).slice(0, 12);
  const selected = assessed.find((candidate) => candidate.eligible) || null;
  return {
    ok: true,
    intent,
    agent,
    cwd: options.cwd || '',
    cached: false,
    generatedAt: new Date().toISOString(),
    skill: selected ? publicCandidate(selected) : null,
    candidates: candidates.map(publicCandidate),
    totalSkills: safeArray(skills).length,
  };
}

function assessSkillForIntent(skill, options = {}) {
  const intent = normalizeIntent(options.intent);
  const agent = skillAutocomplete.normalizeSkillAgentType(options.agent) || 'all';
  const name = String(skill?.name || '').trim();
  const source = String(skill?.source || '');
  const description = String(skill?.description || '');
  const text = searchableSkillText(skill);
  const explicit = explicitCapabilities(skill);
  const inferred = inferCapabilities(skill, explicit);
  const caps = new Set([...explicit, ...inferred]);
  const reasons = [];
  const negative = [];
  const messageOnly = isMessageOnlySkill(skill, text);
  if (messageOnly) negative.push(MESSAGE_ONLY_REASON);

  let eligible = false;
  let score = 0;

  if (intent === 'create_pr') {
    eligible = !messageOnly && hasAny(caps, ['github.pr.create', 'git.pr.create', 'pr.create']);
    score += add(capScore(caps, explicit, 'github.pr.create'), 'creates pull requests', reasons);
    score += add(capScore(caps, explicit, 'git.review'), 'reviews changes', reasons);
    score += add(capScore(caps, explicit, 'git.commit'), 'can commit before PR', reasons);
    score += textBonus(/\bgithub\b|\bpull[- ]?request\b|\bcreate[- ]?pr\b|\bpr\b/, text, 8, 'PR language', reasons);
  } else if (intent === 'commit_main') {
    eligible = !messageOnly && hasAny(caps, ['git.commit']);
    score += add(capScore(caps, explicit, 'git.commit'), 'commits local changes', reasons);
    score += add(capScore(caps, explicit, 'git.review'), 'reviews before commit', reasons);
    score += add(capScore(caps, explicit, 'git.quality'), 'quality gate', reasons);
    score += textBonus(/\bsafe[- ]?commit\b|\breview[- ]?and[- ]?commit\b|\bpre[- ]?commit\b|\btl[- ]?commit\b/, text, 10, 'safe commit language', reasons);
  } else if (intent === 'finish_and_merge') {
    const canCommitOrFinish = hasAny(caps, ['git.commit', 'git.branch.finish']);
    const canMerge = hasAny(caps, ['git.merge.main', 'git.branch.finish']);
    eligible = !messageOnly && canCommitOrFinish && canMerge;
    score += add(capScore(caps, explicit, 'git.commit'), 'commits local changes', reasons);
    score += add(capScore(caps, explicit, 'git.merge.main'), 'merges to main', reasons);
    score += add(capScore(caps, explicit, 'git.branch.finish'), 'finishes development branches', reasons);
    score += add(capScore(caps, explicit, 'git.review'), 'reviews before integration', reasons);
    score += add(capScore(caps, explicit, 'git.quality'), 'quality gate', reasons);
    score += textBonus(/\bto main\b|\bmerge\b.*\bmain\b|\bintegrat\w*\b.*\bmain\b|\bfinish(?:ing)?[- ]a[- ]development[- ]branch\b/, text, 12, 'main integration language', reasons);
  }

  score += sourceBonus(source, agent);
  const frequency = Number(skill?.frequency || 0);
  if (Number.isFinite(frequency) && frequency > 0) score += Math.min(frequency, 20) / 20;
  if (messageOnly) score -= 100;
  if (!eligible) score = Math.max(0, score);

  const prefix = invocationPrefixForSkill(skill, agent);
  return {
    skill,
    name,
    description,
    source,
    agents: Array.isArray(skill?.agents) ? skill.agents : [],
    prefix,
    intent,
    agent,
    score,
    eligible,
    capabilities: [...caps].sort(),
    explicitCapabilities: [...explicit].sort(),
    reasons,
    negative,
    originalIndex: Number.isFinite(options.index) ? options.index : 0,
  };
}

function inferCapabilities(skill, explicit) {
  const text = searchableSkillText(skill);
  const caps = new Set();
  const messageOnly = isMessageOnlySkill(skill, text);
  if (!messageOnly && /\bcommit\b|\bstaged changes\b|\blocal changes\b/.test(text)) caps.add('git.commit');
  if (/\breview\b|\baudit\b|\bverify\b|\bquality\b|\bpre[- ]?commit\b|\bsafe[- ]?commit\b|\btl[- ]?commit\b/.test(text)) caps.add('git.review');
  if (/\bquality\b|\bsafe\b|\bsecurity\b|\bpii\b|\bsecret\b|\bpre[- ]?commit\b|\btl[- ]?commit\b/.test(text)) caps.add('git.quality');
  if (/\bmerge\b.*\bmain\b|\bmain\b.*\bmerge\b|\bintegrat\w*\b.*\bmain\b|\bto main\b/.test(text)) caps.add('git.merge.main');
  if (/\bfinish(?:ing)?[- ]a[- ]development[- ]branch\b|\bcomplete\b.*\bdevelopment work\b|\bguide\w*\b.*\bmerge\b.*\bpr\b.*\bcleanup\b/.test(text)) {
    caps.add('git.branch.finish');
    caps.add('git.merge.main');
  }
  if (/\bcreate\b.*\b(?:pr|pull[- ]?request)\b|\b(?:pr|pull[- ]?request)\b.*\bcreate\b|\bgithub\b.*\b(?:pr|pull[- ]?request)\b/.test(text)) {
    caps.add('github.pr.create');
  }
  for (const cap of explicit) caps.add(cap);
  return caps;
}

function explicitCapabilities(skill) {
  const raw = [
    ...safeArray(skill?.capabilities),
    ...safeArray(skill?.intents),
  ];
  return new Set(skillAutocomplete.normalizeSkillCapabilities(raw).flatMap(expandCapabilityAlias));
}

function expandCapabilityAlias(value) {
  const cap = String(value || '').toLowerCase();
  const out = [cap];
  if (cap === 'commit' || cap === 'git.commit.local') out.push('git.commit');
  if (cap === 'review' || cap === 'code.review') out.push('git.review');
  if (cap === 'quality' || cap === 'safety') out.push('git.quality');
  if (cap === 'merge' || cap === 'merge.main' || cap === 'git.merge') out.push('git.merge.main');
  if (cap === 'finish' || cap === 'finish.branch' || cap === 'branch.finish') out.push('git.branch.finish');
  if (cap === 'pr' || cap === 'pull.request' || cap === 'github.pr') out.push('github.pr.create');
  return out;
}

function isMessageOnlySkill(skill, text = searchableSkillText(skill)) {
  const name = String(skill?.name || '').toLowerCase();
  return /(?:^|[-_.:])commit[-_.:]?messages?(?:$|[-_.:])/.test(name) ||
    /\bwriting[- ]commit[- ]messages?\b/.test(text) ||
    /\bwrite\w*\b.*\bcommit messages?\b/.test(text) ||
    /\bcommit message quality\b/.test(text) ||
    /\bcommit message help\b/.test(text);
}

function invocationPrefixForSkill(skill, agentType) {
  if (skill?.invocation === '$' || skill?.invocation === '/') return skill.invocation;
  const agent = skillAutocomplete.normalizeSkillAgentType(agentType);
  const source = String(skill?.source || '').toLowerCase();
  if (agent === 'claude') return '/';
  if (agent === 'codex' || agent === 'opencode' || agent === 'gemini') return '$';
  if (source.startsWith('claude-')) return '/';
  return '$';
}

function capScore(caps, explicit, cap) {
  if (explicit.has(cap)) return 34;
  if (caps.has(cap)) return 18;
  return 0;
}

function add(value, reason, reasons) {
  if (value > 0 && reason) reasons.push(reason);
  return value;
}

function textBonus(pattern, text, score, reason, reasons) {
  if (!pattern.test(text)) return 0;
  if (reason) reasons.push(reason);
  return score;
}

function sourceBonus(source, agent) {
  const priority = skillAutocomplete.skillSourcePriorityForAgent(source, agent);
  if (priority <= 1) return 3;
  if (priority <= 3) return 2;
  if (priority <= 5) return 1;
  return 0;
}

function hasAny(caps, values) {
  return values.some((value) => caps.has(value));
}

function compareIntentCandidates(a, b) {
  if (a.eligible !== b.eligible) return a.eligible ? -1 : 1;
  if (b.score !== a.score) return b.score - a.score;
  const agent = a.intent === b.intent ? (a.agent || b.agent || 'all') : 'all';
  const sourceDelta = skillAutocomplete.skillSourcePriorityForAgent(a.source, agent) -
    skillAutocomplete.skillSourcePriorityForAgent(b.source, agent);
  if (sourceDelta) return sourceDelta;
  return String(a.name || '').localeCompare(String(b.name || ''), undefined, {
    numeric: true,
    sensitivity: 'base',
  });
}

function publicCandidate(candidate) {
  if (!candidate) return null;
  return {
    name: candidate.name,
    description: candidate.description,
    source: candidate.source,
    agents: candidate.agents,
    prefix: candidate.prefix,
    invocation: candidate.prefix,
    score: Math.round(candidate.score * 100) / 100,
    eligible: !!candidate.eligible,
    capabilities: candidate.capabilities,
    reasons: candidate.reasons.slice(0, 5),
    negative: candidate.negative,
  };
}

function searchableSkillText(skill) {
  return [
    skill?.name,
    skill?.description,
    skill?.execution,
    ...(Array.isArray(skill?.capabilities) ? skill.capabilities : []),
    ...(Array.isArray(skill?.intents) ? skill.intents : []),
  ].join(' ').toLowerCase().replace(/[_:./]+/g, ' ');
}

function normalizeCwd(value) {
  const text = String(value || '').trim();
  return text ? path.resolve(text) : '';
}

function safeArray(value) {
  return Array.isArray(value) ? value : [];
}

function cloneResult(value) {
  return JSON.parse(JSON.stringify(value));
}

function clearSkillIntentCache() {
  intentCache = new Map();
}

function skillIntentCacheStats() {
  return { entries: intentCache.size };
}

module.exports = {
  assessSkillForIntent,
  clearSkillIntentCache,
  invocationPrefixForSkill,
  normalizeIntent,
  resolveSkillIntent,
  resolveSkillIntentFromSkills,
  skillIntentCacheStats,
};
