/**
 * VLGenerate Tool — one-call project generation via codegen workflows
 *
 * This tool is the PRIMARY entry point for generating VL projects.
 * It selects the appropriate workflow based on project size and runs it.
 *
 * Actions:
 *   "generate"  — Run a codegen workflow (auto-selects or user-specified)
 *   "list"      — List available codegen workflows
 *   "status"    — Check if a workflow is currently running
 */
import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import os from 'os';

// Resolve VL-Code install directory (where workflow JSONs live)
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const VL_CODE_INSTALL_DIR = path.resolve(__dirname, '..', '..');  // src/tools/ → vl-code/
const VL_CODE_HOME = path.join(os.homedir(), '.vl-code');

export function createVLGenerateTool(config) {
  return {
    description: `Generate a VL project by running a codegen workflow. This is the PREFERRED way to generate code — it runs the full pipeline (Meta → DB → Theme → Services → Components → Sections → Apps).

Actions:
- "generate": Run codegen workflow. Requires userRequest AND projectName. Optional: workflow, targetLang, isTest.
- "list": List available codegen workflows with descriptions.
- "status": Check workflow engine status.
- "status": Check workflow engine status and current generation workflow progress.

IMPORTANT: You MUST always provide projectName (PascalCase, English-only, e.g. "NewYoutube") when generating.
The tool auto-creates the project directory and switches workspace before running the workflow.
- Production projects → ~/Documents/VLProjects/{projectName}/
- Test/demo projects → ~/Documents/VLProjects/_tests/{projectName}/ (set isTest: true)

The tool auto-selects the best workflow if not specified:
- parallel: DEFAULT — fully parallel, fastest
- meta-direct: Small projects (≤5 pages)
- 3-file / 6-file / 9-file: Medium to large projects

ALWAYS use this tool when the user asks to generate, create, develop, or build a VL project.`,
    parameters: {
      type: 'object',
      properties: {
        action: {
          type: 'string',
          enum: ['generate', 'list', 'status'],
          description: 'Action to perform',
        },
        userRequest: {
          type: 'string',
          description: 'User\'s project requirement description (for "generate" action)',
        },
        projectName: {
          type: 'string',
          description: 'PascalCase English project name (e.g. "NewYoutube"). Auto-creates project directory and switches workspace before running workflow. Required for new projects.',
        },
        isTest: {
          type: 'boolean',
          description: 'If true, creates project under ~/Documents/VLProjects/_tests/ instead of ~/Documents/VLProjects/. Default: false.',
        },
        workflow: {
          type: 'string',
          enum: ['parallel', 'meta-direct', '3-file', '6-file', '9-file'],
          description: 'Which codegen workflow to use. If omitted, uses server-configured default or auto-selects based on project complexity.',
        },
        targetLang: {
          type: 'string',
          description: 'Target language for generated content (default: "zh-CN")',
        },
      },
      required: ['action'],
    },
    execute: async (input) => {
      switch (input.action) {
        case 'generate':
          return runGenerate(config, input);
        case 'list':
          return listWorkflows(config);
        case 'status':
          return JSON.stringify(buildWorkflowStatus(config), null, 2);
        default:
          return 'Unknown action. Use: generate, list, status.';
      }
    },
  };
}

/**
 * Map workflow shorthand to actual file path
 */
const WORKFLOW_MAP = {
  'parallel': '.vl-code/workflows/parallel-codegen.json',
  'meta-direct': '.vl-code/workflows/meta-direct-codegen.json',
  '3-file': '.vl-code/workflows/3-file-codegen.json',
  '6-file': '.vl-code/workflows/6-file-codegen.json',
  '9-file': '.vl-code/workflows/9-file-codegen.json',
};

const TEST_PROJECTS_DIR = path.join(os.homedir(), 'Documents', 'VLProjects', '_tests');
const PROD_PROJECTS_DIR = path.join(os.homedir(), 'Documents', 'VLProjects');

function buildWorkflowStatus(config) {
  const workflowState = typeof config._getWorkflowState === 'function'
    ? config._getWorkflowState()
    : null;
  const workDir = workflowState?.workDir || config.workDir || '';
  return {
    available: true,
    engine: 'vl-workflow-engine (JS)',
    spec: 'v3.16',
    active: !!workflowState?.active,
    workflowName: workflowState?.workflowName || null,
    nodeStatuses: workflowState?.nodeStatuses || {},
    nodeDetails: workflowState?.nodeDetails || {},
    timeline: workflowState?.timeline || [],
    startedAt: workflowState?.startedAt || null,
    finishedAt: workflowState?.finishedAt || null,
    lastError: workflowState?.lastError || null,
    workDir,
    projectName: workDir ? path.basename(workDir) : null,
    message: workflowState?.active
      ? 'A generation workflow is still running. Poll status until active=false before compiling or running AutoTestPipeline.'
      : 'No generation workflow is active.',
  };
}

export function sanitizeProjectName(rawName) {
  if (!rawName) return '';
  const cleaned = String(rawName)
    .replace(/[`"'“”‘’]/g, ' ')
    .replace(/[^A-Za-z0-9]+/g, ' ')
    .trim();
  if (!cleaned) return '';
  const parts = cleaned.split(/\s+/).filter(Boolean);
  const merged = parts
    .map(part => part.charAt(0).toUpperCase() + part.slice(1).replace(/[^A-Za-z0-9]/g, ''))
    .join('');
  return /^[A-Z][A-Za-z0-9]*$/.test(merged) ? merged : '';
}

export function inferProjectName(userRequest) {
  if (!userRequest) return '';
  const patterns = [
    /([A-Za-z][A-Za-z0-9_-]{1,40})[^A-Za-z]{0,8}(?:项目|应用|app|project)/i,
    /(?:项目|应用|app|project)[^A-Za-z]{0,8}([A-Za-z][A-Za-z0-9_-]{1,40})/i,
    /\b([A-Za-z][A-Za-z0-9_-]{2,40}(?:App|Project|System|Demo|Test))\b/,
  ];
  const blocked = new Set(['for', 'with', 'from', 'into', 'demo', 'test', 'app', 'project']);
  for (const pattern of patterns) {
    const match = String(userRequest).match(pattern);
    const inferred = sanitizeProjectName(match?.[1]);
    if (inferred && !blocked.has(inferred.toLowerCase())) return inferred;
  }
  return '';
}

export function shouldDefaultToTestProject(userRequest, currentWorkDir) {
  if (isTestWorkspacePath(currentWorkDir)) return true;
  return /(^|[\s,，。])(?:test|demo|sample|example|试试|测试|示例|演示)([\s,，。]|$)/i.test(String(userRequest || ''));
}

/**
 * Auto-select workflow based on user request complexity
 */
function autoSelectWorkflow(userRequest) {
  const len = userRequest.length;

  // Count indicators of complexity (no /g flag — .test() with /g is stateful)
  const complexityIndicators = [
    /(\d+)\s*(个|pages?|页面|模块|modules?)/i,
    /多角色|multi.?role|权限|permission/i,
    /大型|large|enterprise|企业/i,
    /复杂|complex|complete/i,
  ];

  let complexity = 0;

  // Check page/module count mentions (handle "10+" or "10个" etc.)
  const countMatch = userRequest.match(/(\d+)\+?\s*(个|pages?|页面|模块|modules?|端|apps?|应用)/i);
  if (countMatch) {
    const count = parseInt(countMatch[1], 10);
    if (count > 8) complexity += 3;
    else if (count > 4) complexity += 2;
    else complexity += 1;
  }

  // Check complexity keywords
  for (const pattern of complexityIndicators) {
    if (pattern.test(userRequest)) complexity += 1;
  }

  // Longer descriptions usually mean more complex
  if (len > 500) complexity += 2;
  else if (len > 200) complexity += 1;

  if (complexity >= 5) return '9-file';
  if (complexity >= 3) return '6-file';
  if (complexity >= 2) return '3-file';
  return 'parallel';
}

export function isTestWorkspacePath(dirPath) {
  if (!dirPath) return false;
  const normalized = path.resolve(dirPath).replace(/\\/g, '/');
  return /\/Documents\/VLProjects\/_tests(?:\/|$)/.test(normalized);
}

export function resolveGenerationProjectDir({ currentWorkDir, projectName, isTest }) {
  const explicitIsTest = typeof isTest === 'boolean' ? isTest : null;
  const currentDir = currentWorkDir ? path.resolve(currentWorkDir) : '';
  const prodCandidate = path.join(PROD_PROJECTS_DIR, projectName);
  const testCandidate = path.join(TEST_PROJECTS_DIR, projectName);

  if (explicitIsTest === null) {
    if (currentDir && path.basename(currentDir) === projectName) {
      return currentDir;
    }

    const testExists = fsSync.existsSync(testCandidate);
    const prodExists = fsSync.existsSync(prodCandidate);
    if (testExists && !prodExists) return testCandidate;
  }

  const useTestDir = explicitIsTest ?? isTestWorkspacePath(currentDir);
  return path.join(useTestDir ? TEST_PROJECTS_DIR : PROD_PROJECTS_DIR, projectName);
}

async function runGenerate(config, input) {
  const { userRequest, workflow, targetLang = 'zh-CN' } = input;
  const inferredProjectName = sanitizeProjectName(input.projectName) || inferProjectName(userRequest);
  const projectName = inferredProjectName || `GeneratedApp${new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14)}`;
  const explicitIsTest = typeof input.isTest === 'boolean'
    ? input.isTest
    : (config.noWorkspace && shouldDefaultToTestProject(userRequest, config.workDir) ? true : null);

  if (!userRequest) {
    return 'Error: userRequest is required. Provide the project requirement description.';
  }

  // Auto-create project directory and switch workspace if projectName is provided
  if (projectName) {
    const projDir = resolveGenerationProjectDir({
      currentWorkDir: config.workDir,
      projectName,
      isTest: explicitIsTest,
    });

    // Create project structure
    if (!fsSync.existsSync(projDir)) {
      fsSync.mkdirSync(projDir, { recursive: true });
    }
    for (const d of ['Config', 'Apps', 'Sections', 'ExtComponents', 'Services', 'Database', 'Theme', 'Process', '.vl-code']) {
      const dp = path.join(projDir, d);
      if (!fsSync.existsSync(dp)) fsSync.mkdirSync(dp, { recursive: true });
    }

    // Switch workDir so workflow writes to the new project
    config.workDir = projDir;

    // Notify server to switch workspace (if _switchWorkspace callback is available)
    if (config._switchWorkspace) {
      await config._switchWorkspace(projDir, projectName);
    }

    const broadcast = config._broadcast || (() => {});
    broadcast({ type: 'workspace_switched', workDir: projDir });
  }

  // Select workflow: explicit > server-configured default > auto-select
  let selectedWorkflow = workflow;
  if (!selectedWorkflow) {
    // Check server-configured default (stored in .vl-code/project.json)
    try {
      const projJsonPath = path.join(config.workDir, '.vl-code', 'project.json');
      if (fsSync.existsSync(projJsonPath)) {
        const projJson = JSON.parse(fsSync.readFileSync(projJsonPath, 'utf-8'));
        if (projJson.defaultWorkflow && WORKFLOW_MAP[projJson.defaultWorkflow]) {
          selectedWorkflow = projJson.defaultWorkflow;
        }
      }
    } catch {}
  }
  if (!selectedWorkflow) {
    selectedWorkflow = autoSelectWorkflow(userRequest);
  }
  const workflowPath = WORKFLOW_MAP[selectedWorkflow];

  if (!workflowPath) {
    return `Error: Unknown workflow "${selectedWorkflow}". Available: parallel, meta-direct, 3-file, 6-file, 9-file.`;
  }

  // Search multiple directories for the workflow file (project first, then VL-Code install, then global)
  const searchDirs = [
    path.join(config.workDir, '.vl-code', 'workflows'),
    path.join(VL_CODE_INSTALL_DIR, '.vl-code', 'workflows'),
    path.join(VL_CODE_HOME, 'workflows'),
  ];

  let wfJson = null;
  let foundPath = null;
  const wfFile = workflowPath.includes('/') ? path.basename(workflowPath) : workflowPath;

  for (const dir of searchDirs) {
    const fp = path.join(dir, wfFile);
    if (fsSync.existsSync(fp)) {
      try {
        wfJson = JSON.parse(fsSync.readFileSync(fp, 'utf-8'));
        foundPath = fp;
        break;
      } catch {}
    }
  }

  if (!wfJson) {
    return `Error: Workflow file "${wfFile}" not found in any of:\n${searchDirs.map(d => '  - ' + d).join('\n')}`;
  }

  // Build params based on workflow type
  const params = { targetLang };
  if (selectedWorkflow === '9-file') {
    // 9-file uses $userRequirement
    params.userRequirement = userRequest;
    params.userRequest = userRequest;
  } else {
    params.userRequest = userRequest;
  }

  // Broadcast helper — sends events to frontend via SSE for live visualization
  const broadcast = config._broadcast || (() => {});
  const setWorkflowState = config._onWorkflowNodeStatus || (() => {});
  const recordWorkflowEvent = config._recordWorkflowEvent || (() => {});

  try {
    const { WorkflowExecutor } = await import('../vl/workflow-executor.js');
    const executor = new WorkflowExecutor({
      apiKey: config.apiKey,
      model: config.model,
      llmProvider: config.llmProvider,
      workDir: config.workDir,
      cookie: config.cookie,
      vlVersion: config.vlVersion,
      toolRegistry: config.toolRegistry || config._toolRegistry || null,
    });

    // Register as the active executor so pause/resume works via /api/workflow/resume
    if (config._setActiveExecutor) config._setActiveExecutor(executor);
    if (config._onWorkflowStart) config._onWorkflowStart(selectedWorkflow, { stepCount: wfJson.steps?.length || 0 });

    // Notify frontend: switch to Flow tab + load workflow DAG
    broadcast({
      type: 'workflow_execution_start',
      workflowName: selectedWorkflow,
      workflow: wfJson,
      stepCount: wfJson.steps?.length || 0,
    });
    broadcast({ type: 'wf_start', workflowName: selectedWorkflow, stepCount: wfJson.steps?.length || 0, model: config.model || 'unknown' });
    recordWorkflowEvent({ type: 'wf_start', workflowName: selectedWorkflow, stepCount: wfJson.steps?.length || 0, model: config.model || 'unknown' });

    // Fire-and-forget: run workflow in background so the tool returns immediately.
    // Progress is tracked via SSE broadcasts to the Flow tab (node highlighting).
    // Full event callbacks matching the workflow route for complete UI integration.
    executor.execute(wfJson, params, {
      onNodeStart: (info) => {
        setWorkflowState(info.nodeId, 'running', {
          title: info.title,
          type: info.type,
          input: info.input,
          resolvedInputs: info.resolvedInputs,
        });
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'running' });
        broadcast({ type: 'wf_node_start', nodeId: info.nodeId, title: info.title, nodeType: info.type, input: info.input, resolvedInputs: info.resolvedInputs });
        recordWorkflowEvent({ type: 'wf_node_start', nodeId: info.nodeId, status: 'running', title: info.title, nodeType: info.type, input: info.input, resolvedInputs: info.resolvedInputs });
      },
      onNodeDone: (info) => {
        setWorkflowState(info.nodeId, 'done', {
          title: info.title,
          output: info.output,
          outputs: info.outputs,
          selected: info.selected,
          duration_ms: info.duration_ms,
        });
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'done' });
        broadcast({ type: 'wf_node_done', nodeId: info.nodeId, title: info.title, duration_ms: info.duration_ms, output: info.output, outputs: info.outputs, selected: info.selected });
        recordWorkflowEvent({ type: 'wf_node_done', nodeId: info.nodeId, status: 'done', title: info.title, duration_ms: info.duration_ms, output: info.output, outputs: info.outputs, selected: info.selected });
      },
      onNodeError: (info) => {
        setWorkflowState(info.nodeId, 'error', {
          title: info.title,
          type: info.type,
          error: info.error,
          detail: info.detail,
          duration_ms: info.duration_ms,
        });
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'error' });
        broadcast({ type: 'wf_node_error', nodeId: info.nodeId, title: info.title, error: info.error, detail: info.detail, duration_ms: info.duration_ms });
        recordWorkflowEvent({ type: 'wf_node_error', nodeId: info.nodeId, status: 'error', title: info.title, nodeType: info.type, error: info.error, detail: info.detail, duration_ms: info.duration_ms });
      },
      onNodeSkipped: (info) => {
        setWorkflowState(info.nodeId, 'skipped', { reason: info.reason || 'condition false' });
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'skipped' });
        broadcast({ type: 'wf_node_skipped', nodeId: info.nodeId });
        recordWorkflowEvent({ type: 'wf_node_skipped', nodeId: info.nodeId, status: 'skipped', reason: info.reason || 'condition false' });
      },
      onPause: (info) => {
        setWorkflowState(info.nodeId, 'paused', info);
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'paused' });
        broadcast({ type: 'wf_pause', ...info });
        recordWorkflowEvent({ type: 'wf_pause', nodeId: info.nodeId, status: 'paused', title: info.title, reason: info.reason });
      },
      onResumed: (info) => {
        setWorkflowState(info.nodeId, 'running', info);
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'running' });
        broadcast({ type: 'wf_resumed', ...info });
        recordWorkflowEvent({ type: 'wf_resumed', nodeId: info.nodeId, status: 'running' });
      },
      onVarsChanged: (nodeId, vars) => {
        broadcast({ type: 'wf_vars', nodeId, vars });
        recordWorkflowEvent({ type: 'wf_vars', nodeId, output: vars });
      },
      onLLMThinking: (info) => {
        broadcast({ type: 'wf_llm_thinking', ...info });
        recordWorkflowEvent({ type: 'wf_llm_thinking', nodeId: info.stepId, text: info.delta });
      },
      onLLMToolUse: (info) => {
        broadcast({ type: 'wf_llm_tool_use', ...info });
        recordWorkflowEvent({ type: 'wf_llm_tool_use', nodeId: info.stepId, name: info.name, input: info.input, tool_use_id: info.tool_use_id });
      },
      onLLMToolResult: (info) => {
        broadcast({ type: 'wf_llm_tool_result', ...info });
        recordWorkflowEvent({ type: 'wf_llm_tool_result', nodeId: info.stepId, tool_use_id: info.tool_use_id, output: info.content, error: info.is_error ? info.content : undefined });
      },
      onLLMDone: (info) => {
        broadcast({ type: 'wf_llm_done', ...info });
        recordWorkflowEvent({ type: 'wf_llm_done', nodeId: info.stepId, model: info.model, usage: info.usage, duration_ms: info.latency_ms });
      },
      onLLMError: (info) => {
        broadcast({ type: 'wf_llm_error', ...info });
        recordWorkflowEvent({ type: 'wf_llm_error', nodeId: info.stepId, error: info.error, detail: info.code, duration_ms: info.latency_ms });
      },
      onToolStart: (info) => {
        broadcast({ type: 'wf_tool_start', ...info });
        recordWorkflowEvent({ type: 'wf_tool_start', nodeId: info.stepId, name: info.name, input: info.input });
      },
      onToolDone: (info) => {
        broadcast({ type: 'wf_tool_done', ...info });
        recordWorkflowEvent({ type: 'wf_tool_done', nodeId: info.stepId, name: info.name, output: info.output });
      },
      onToolError: (info) => {
        broadcast({ type: 'wf_tool_error', ...info });
        recordWorkflowEvent({ type: 'wf_tool_error', nodeId: info.stepId, name: info.name, error: info.error, output: info.output });
      },
      onToolMessage: (info) => {
        broadcast({ type: 'wf_tool_message', ...info });
        recordWorkflowEvent({ type: 'wf_tool_message', nodeId: info.stepId, name: info.name, level: info.level, message: info.message, output: info.data });
      },
      onFileStart: (fp) => {
        broadcast({ type: 'wf_file_start', path: fp });
        recordWorkflowEvent({ type: 'wf_file_start', path: fp });
      },
      onVarChanged: (info) => {
        broadcast({ type: 'wf_var_changed', ...info });
        recordWorkflowEvent({ type: 'wf_var_changed', nodeId: info.stepId, title: info.name, output: info.newValue });
      },
      onFileWritten: (fp) => {
        broadcast({ type: 'file_changed', path: fp });
        broadcast({ type: 'wf_file_done', path: fp });
        recordWorkflowEvent({ type: 'wf_file_done', path: fp });
      },
      onFileChanged: (fp, sizeBytes) => {
        broadcast({ type: 'file_changed', path: fp, size_bytes: sizeBytes });
      },
      onToken: (token) => {
        broadcast({ type: 'wf_token', token });
      },
      onText: (text) => {
        broadcast({ type: 'wf_text', text });
        recordWorkflowEvent({ type: 'wf_text', text });
      },
      onCheckpoint: (cp) => {
        broadcast({ type: 'wf_checkpoint', runID: cp.workflowID, stepID: cp.currentStepID, completedSteps: cp.completedSteps, checkpoint: cp });
        recordWorkflowEvent({ type: 'wf_checkpoint', stepID: cp.currentStepID, completedSteps: cp.completedSteps, runID: cp.workflowID });
      },
      onDone: (info) => {
        // Use server callback to properly scan project and extract meta
        if (config._onWorkflowDone) {
          config._onWorkflowDone();
        } else {
          if (config._setActiveExecutor) config._setActiveExecutor(null);
          broadcast({ type: 'project_reloaded' });
        }
        broadcast({ type: 'wf_done', workflowName: selectedWorkflow, ...info });
        recordWorkflowEvent({ type: 'wf_done', workflowName: selectedWorkflow, output: info });
      },
      onError: (msg) => {
        if (config._setActiveExecutor) config._setActiveExecutor(null);
        if (config._onWorkflowError) config._onWorkflowError(selectedWorkflow, msg);
        broadcast({ type: 'wf_error', workflowName: selectedWorkflow, error: msg });
        recordWorkflowEvent({ type: 'wf_error', workflowName: selectedWorkflow, error: msg });
      },
    }).catch(err => {
      if (config._setActiveExecutor) config._setActiveExecutor(null);
      if (config._onWorkflowError) config._onWorkflowError(selectedWorkflow, err.message);
      broadcast({ type: 'wf_error', workflowName: selectedWorkflow, error: err.message });
      recordWorkflowEvent({ type: 'wf_error', workflowName: selectedWorkflow, error: err.message });
    });

    // Return immediately — workflow runs in background
    return JSON.stringify({
      status: 'started',
      projectName,
      workflow: selectedWorkflow,
      workflowName: wfJson.name,
      stepCount: wfJson.steps?.length || 0,
      message: `Workflow "${wfJson.name}" started for project "${projectName}". Watch the Flow tab for real-time progress. Each node will highlight as it runs (orange=running, green=done, red=error).`,
    }, null, 2);
  } catch (err) {
    return `Workflow execution failed: ${err.message}`;
  }
}

async function listWorkflows(config) {
  const workflows = [];
  const searchDirs = [
    path.join(config.workDir, '.vl-code', 'workflows'),
    path.join(VL_CODE_INSTALL_DIR, '.vl-code', 'workflows'),
    path.join(VL_CODE_HOME, 'workflows'),
  ];

  for (const [key, relPath] of Object.entries(WORKFLOW_MAP)) {
    const wfFile = path.basename(relPath);
    let info = { id: key, path: relPath, available: false };

    // Search all directories
    for (const dir of searchDirs) {
      const fp = path.join(dir, wfFile);
      if (fsSync.existsSync(fp)) {
        try {
          const content = fsSync.readFileSync(fp, 'utf-8');
          const wf = JSON.parse(content);
          info.available = true;
          info.name = wf.name;
          info.description = wf.description || '';
          info.version = wf.version;
          info.stepCount = wf.steps?.length || 0;
          info.params = wf.registry?.params || [];
          break;
        } catch {}
      }
    }

    workflows.push(info);
  }

  return JSON.stringify(workflows, null, 2);
}
