// v2.8.0: Workflow catalog — built-in workflow definitions that ship with
// BooCode. Each workflow is a metadata object with name, description, and a
// factory function that returns the workflow script source code.
//
// Built-in workflows are merged into the discovery list alongside file-based
// workflows from .boocode/workflows/. They take precedence over user-defined
// workflows with the same name.

import { createHash } from 'node:crypto';

/**
 * A built-in workflow definition shipped with BooCode.
 */
export interface BuiltinWorkflow {
  /** Unique workflow name (used to invoke via `WorkflowManager`). */
  name: string;
  /** Human-readable description of what this workflow does. */
  description: string;
  /** Optional ordered phases for UI progress display. */
  phases?: Array<{ title: string; detail?: string }>;
  /**
   * Generate the workflow script source code for this workflow.
   * The returned string must be valid JS that exports `meta` and a `default`
   * async function matching the `WorkflowScript` shape.
   *
   * @param args - Optional arguments provided when the workflow is started.
   */
  generateScript: (args?: Record<string, unknown>) => string;
}

/**
 * Stable JSON serialisation for generating deterministic cache keys from
 * structured arguments. Keys are sorted so the same data always produces
 * the same string regardless of property insertion order.
 */
function stableJson(value: unknown): string {
  if (value === null) return 'null';
  if (typeof value !== 'object') return JSON.stringify(value);
  if (Array.isArray(value)) {
    return `[${value.map(stableJson).join(',')}]`;
  }
  const keys = Object.keys(value as Record<string, unknown>).sort();
  const pairs = keys.map((k) => `${JSON.stringify(k)}:${stableJson((value as Record<string, unknown>)[k])}`);
  return `{${pairs.join(',')}}`;
}

export function fingerprintAgentTask(
  prompt: string,
  spec: Record<string, unknown>,
  args: string,
): string {
  return createHash('sha256')
    .update(stableJson({ prompt, spec, args }))
    .digest('hex');
}

function generateDeepResearchScript(_args?: Record<string, unknown>): string {
  return `
export const meta = {
  name: 'deep-research',
  description: 'Multi-phase deep research: scope, search, fetch, verify, synthesise.',
  phases: [
    { title: 'Scope', detail: 'Define the research question and search criteria' },
    { title: 'Search', detail: 'Query web sources in parallel' },
    { title: 'Fetch', detail: 'Retrieve full content from top sources' },
    { title: 'Verify', detail: 'Cross-reference and validate findings' },
    { title: 'Synthesise', detail: 'Produce a final structured report' },
  ],
};

export default async function main(args) {
  const query = args?.query ?? 'No query provided';
  log('deep-research: starting with query: ' + query);

  phase('Scope');
  const scope = await agent(
    'Analyse this research query and produce a search plan with 3-5 key sub-questions: ' + query,
    { label: 'scope-analysis', phase: 'scope' },
  );
  log('Scope completed');

  phase('Search');
  const searchResults = await agent(
    'Based on the scope, search for authoritative sources. Return a list of 3-5 URLs with brief annotations.',
    { label: 'web-search', phase: 'search' },
  );
  log('Search completed');

  phase('Fetch');
  const fetchedContent = await agent(
    'Extract and summarise the key information from these sources: ' + JSON.stringify(searchResults),
    { label: 'content-fetch', phase: 'fetch' },
  );
  log('Fetch completed');

  phase('Verify');
  const verified = await agent(
    'Cross-reference the fetched information. Note any contradictions, gaps, or weak sources: ' + JSON.stringify(fetchedContent),
    { label: 'verification', phase: 'verify' },
  );
  log('Verify completed');

  phase('Synthesise');
  const report = await agent(
    'Synthesise the verified information into a structured report with findings, sources, and confidence levels: ' + JSON.stringify(verified),
    { label: 'synthesis', phase: 'synthesise' },
  );
  log('deep-research: completed');

  return {
    ok: true,
    output: report,
    phases: { scope, searchResults, fetchedContent, verified, report },
  };
}
`.trim();
}

function generateReviewCodeScript(_args?: Record<string, unknown>): string {
  return `
export const meta = {
  name: 'review-code',
  description: 'Multi-perspective code review: correctness, security, performance, then synthesise.',
  phases: [
    { title: 'Correctness', detail: 'Check logic, edge cases, and correctness' },
    { title: 'Security', detail: 'Analyse for vulnerabilities and unsafe patterns' },
    { title: 'Performance', detail: 'Identify performance bottlenecks and optimisation opportunities' },
    { title: 'Synthesise', detail: 'Merge perspectives into a unified review report' },
  ],
};

export default async function main(args) {
  const target = args?.target ?? args?.path ?? '';
  log('review-code: starting review of: ' + (target || '(no target specified)'));

  const context = await agent(
    'Read the code at ' + (target || 'the provided context') + ' and produce a summary of its structure and purpose.',
    { label: 'read-context', phase: 'context' },
  );

  phase('Correctness');
  const correctness = await agent(
    'Review this code for correctness. Check logical errors, edge cases, type safety, and concurrency issues:\\n' + JSON.stringify(context),
    { label: 'correctness-review', phase: 'correctness' },
  );

  phase('Security');
  const security = await agent(
    'Review this code for security vulnerabilities. Check for injection, auth bypasses, unsafe deserialisation, secret exposure:\\n' + JSON.stringify(context),
    { label: 'security-review', phase: 'security' },
  );

  phase('Performance');
  const performance = await agent(
    'Review this code for performance issues. Check algorithmic complexity, unnecessary allocations, I/O patterns, caching opportunities:\\n' + JSON.stringify(context),
    { label: 'performance-review', phase: 'performance' },
  );

  phase('Synthesise');
  const report = await agent(
    'Merge these three review perspectives into one structured report with severity-ranked findings:\\n' +
    '--- Correctness ---\\n' + JSON.stringify(correctness) + '\\n' +
    '--- Security ---\\n' + JSON.stringify(security) + '\\n' +
    '--- Performance ---\\n' + JSON.stringify(performance),
    { label: 'synthesis', phase: 'synthesise' },
  );
  log('review-code: completed');

  return {
    ok: true,
    output: report,
    reviews: { correctness, security, performance },
  };
}
`.trim();
}

function generateFindIssuesScript(_args?: Record<string, unknown>): string {
  return `
export const meta = {
  name: 'find-issues',
  description: 'Iterative issue discovery — keep surfacing issues until consecutive rounds find nothing new.',
  phases: [
    { title: 'Analyse', detail: 'Analyse the codebase for issues' },
    { title: 'Check dry', detail: 'Verify no new issues remain' },
  ],
};

export default async function main(args) {
  const target = args?.target ?? args?.path ?? '.';
  const maxRounds = args?.maxRounds ?? 5;
  log('find-issues: starting on ' + target + ' (max ' + maxRounds + ' rounds)');

  const allIssues = [];
  let dryRounds = 0;
  let round = 0;

  while (dryRounds < 2 && round < maxRounds) {
    round++;
    phase('Analyse');

    const context = allIssues.length > 0
      ? 'Previously found issues (exclude these):\\n' + JSON.stringify(allIssues)
      : 'No issues found yet.';

    const newIssues = await agent(
      'Analyse ' + target + ' for bugs, code smells, and anti-patterns.\\n' + context + '\\nReturn a JSON array of issues. If none found, return an empty array.',
      { label: 'round-' + round + '-analysis', phase: 'analyse' },
    );

    let parsed: unknown[] = [];
    try {
      if (typeof newIssues === 'string') {
        parsed = JSON.parse(newIssues);
      } else if (Array.isArray(newIssues)) {
        parsed = newIssues;
      }
    } catch {
      parsed = [];
    }

    if (parsed.length === 0) {
      dryRounds++;
      phase('Check dry');
      log('Round ' + round + ': no new issues found (dry run ' + dryRounds + '/2)');
    } else {
      dryRounds = 0;
      for (const issue of parsed) {
        allIssues.push(issue);
      }
      log('Round ' + round + ': found ' + parsed.length + ' new issue(s)');
    }
  }

  log('find-issues: completed after ' + round + ' rounds, ' + allIssues.length + ' total issues');

  return {
    ok: true,
    output: allIssues,
    totalRounds: round,
    totalIssues: allIssues.length,
  };
}
`.trim();
}

/**
 * All built-in workflow definitions shipped with BooCode.
 */
const BUILTIN_WORKFLOWS: BuiltinWorkflow[] = [
  {
    name: 'deep-research',
    description:
      'Performs multi-phase deep research: scope the question, search web sources in parallel, fetch full content, verify findings, and synthesise a structured report.',
    phases: [
      { title: 'Scope', detail: 'Define the research question and search criteria' },
      { title: 'Search', detail: 'Query web sources in parallel' },
      { title: 'Fetch', detail: 'Retrieve full content from top sources' },
      { title: 'Verify', detail: 'Cross-reference and validate findings' },
      { title: 'Synthesise', detail: 'Produce a final structured report' },
    ],
    generateScript: generateDeepResearchScript,
  },
  {
    name: 'review-code',
    description:
      'Multi-perspective code review that analyses code for correctness, security vulnerabilities, and performance issues in parallel, then merges findings into a unified severity-ranked report.',
    phases: [
      { title: 'Correctness', detail: 'Check logic, edge cases, and correctness' },
      { title: 'Security', detail: 'Analyse for vulnerabilities and unsafe patterns' },
      { title: 'Performance', detail: 'Identify performance bottlenecks' },
      { title: 'Synthesise', detail: 'Merge perspectives into a unified report' },
    ],
    generateScript: generateReviewCodeScript,
  },
  {
    name: 'find-issues',
    description:
      'Iterative issue discovery that runs analysis rounds until two consecutive passes find nothing new, ensuring comprehensive coverage without infinite loops.',
    phases: [
      { title: 'Analyse', detail: 'Analyse the codebase for issues' },
      { title: 'Check dry', detail: 'Verify no new issues remain' },
    ],
    generateScript: generateFindIssuesScript,
  },
];

/**
 * Read-only map of built-in workflows keyed by name.
 */
const BUILTIN_WORKFLOW_MAP = new Map<string, BuiltinWorkflow>(
  BUILTIN_WORKFLOWS.map((w) => [w.name, w]),
);

/**
 * Return all built-in workflow definitions.
 */
export function getBuiltinWorkflows(): BuiltinWorkflow[] {
  return BUILTIN_WORKFLOWS;
}

/**
 * Look up a built-in workflow by name.
 *
 * @param name - Workflow name (e.g. 'deep-research').
 * @returns The built-in workflow, or undefined if not found.
 */
export function getBuiltinWorkflow(name: string): BuiltinWorkflow | undefined {
  return BUILTIN_WORKFLOW_MAP.get(name);
}

/**
 * Merge built-in workflow metadata into a list of file-discovered workflow
 * entries. Built-in entries take precedence — if a user has a file-based
 * workflow with the same name, the built-in version wins.
 *
 * @param fileWorkflows - Workflow metadata discovered from the filesystem.
 * @returns Merged array with built-in workflows injected and duplicate names
 *          resolved (built-in wins).
 */
export function mergeBuiltinWorkflows(
  fileWorkflows: Array<{ name: string; description: string; sourceFile?: string }>,
): Array<{ name: string; description: string; sourceFile?: string }> {
  const seen = new Set<string>();
  const result: Array<{ name: string; description: string; sourceFile?: string }> = [];

  // Built-in workflows first (they take precedence)
  for (const builtin of BUILTIN_WORKFLOWS) {
    seen.add(builtin.name);
    result.push({
      name: builtin.name,
      description: builtin.description,
      // No sourceFile — built-in workflows are generated, not read from disk
    });
  }

  // File-discovered workflows — skip any name already claimed by built-in
  for (const fw of fileWorkflows) {
    if (seen.has(fw.name)) continue;
    seen.add(fw.name);
    result.push(fw);
  }

  return result;
}
