/**
 * SelfModify Tool - lets VLCode-Lite AI modify its own source, tools, and workflows.
 *
 * Works in all modes:
 *   - Node.js dev/server mode: writes directly to source tree
 *   - Electron dev:            writes directly to source tree
 *   - Electron packaged:       source reads from asar.unpacked (real files),
 *                              tool writes go to ~/.vl-code/tools/ (persists across updates)
 *
 * Actions:
 *   read_source    - read a file from the VLCode-Lite source tree
 *   write_source   - create/overwrite a source file (or user-tool file in packaged mode)
 *   edit_source    - exact-string-replace edit a source file
 *   list_source    - glob the source tree
 *   register_tool  - add import + register call in index.js (dev) or user-registry (packaged)
 *   add_workflow   - save workflow JSON to ~/.vl-code/workflows/ (instant, no restart needed)
 *   restart_server - restart server after source changes
 */
import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';
import os from 'os';
import { exec } from 'child_process';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// Compute paths based on environment
const IS_PACKAGED = process.env.VLCODE_IS_PACKAGED === '1';

// Install dir: for READING source (works in both dev and packaged/asar)
export const VL_CODE_INSTALL_DIR = process.env.VLCODE_APP_ROOT
  || path.resolve(__dirname, '..', '..');

// Writable dir: for WRITING source files
//   - Packaged: process.resourcesPath/app.asar.unpacked (real files, writable, but reset on update)
//   - Dev: same as install dir
const WRITABLE_SRC_DIR = process.env.VLCODE_UNPACKED_DIR
  || path.resolve(__dirname, '..', '..');

// User extension dir: ~/.vl-code/tools/ — ALWAYS writable, persists across updates
const USER_TOOLS_DIR = path.join(os.homedir(), '.vl-code', 'tools');
const USER_TOOL_REGISTRY = path.join(os.homedir(), '.vl-code', 'tools', '_registry.json');

function escapeRegex(text) {
  return text.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
}

function globToRegExp(pattern) {
  let regex = '^';
  for (let i = 0; i < pattern.length; i++) {
    const char = pattern[i];
    const next = pattern[i + 1];
    if (char === '*' && next === '*') {
      const afterNext = pattern[i + 2];
      if (afterNext === '/') {
        regex += '(?:.*/)?';
        i += 2;
      } else {
        regex += '.*';
        i += 1;
      }
      continue;
    }
    if (char === '*') {
      regex += '[^/]*';
      continue;
    }
    if (char === '?') {
      regex += '[^/]';
      continue;
    }
    regex += escapeRegex(char);
  }
  regex += '$';
  return new RegExp(regex);
}

async function listMatchingFiles(rootDir, pattern, ignore = []) {
  const matcher = globToRegExp(pattern);
  const ignoreMatchers = ignore.map(globToRegExp);
  const results = [];

  async function walk(currentDir) {
    const entries = await fs.readdir(currentDir, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = path.join(currentDir, entry.name);
      const relativePath = path.relative(rootDir, fullPath).split(path.sep).join('/');
      if (ignoreMatchers.some((rule) => rule.test(relativePath) || rule.test(`${relativePath}/`))) {
        continue;
      }
      if (entry.isDirectory()) {
        await walk(fullPath);
        continue;
      }
      if (matcher.test(relativePath)) {
        results.push(relativePath);
      }
    }
  }

  await walk(rootDir);
  return results;
}

export function createSelfModifyTool(config) {
  const installDir = VL_CODE_INSTALL_DIR;
  const writableSrcDir = WRITABLE_SRC_DIR;
  const homeDir = os.homedir();

  return {
    description: IS_PACKAGED
      ? `Modify VLCode-Lite itself (Electron packaged mode). Tool source writes go to ~/.vl-code/tools/ (persistent). Install dir (read): ${installDir}. Writable dir: ${writableSrcDir}`
      : `Modify VLCode-Lite itself. Reads and writes source at ${installDir}. User tools also loadable from ~/.vl-code/tools/.`,
    parameters: {
      type: 'object',
      properties: {
        action: {
          type: 'string',
          enum: ['read_source', 'write_source', 'edit_source', 'list_source', 'register_tool', 'add_workflow', 'restart_server'],
          description: 'Action to perform',
        },
        path: {
          type: 'string',
          description: 'Relative path from VLCode-Lite root (e.g. "src/tools/my-tool.js"). For write_source in packaged mode, paths under "src/tools/" go to ~/.vl-code/tools/ automatically.',
        },
        content: {
          type: 'string',
          description: 'File content. Used by write_source.',
        },
        old_string: {
          type: 'string',
          description: 'Exact string to find and replace. Used by edit_source.',
        },
        new_string: {
          type: 'string',
          description: 'Replacement string. Used by edit_source.',
        },
        tool_name: {
          type: 'string',
          description: 'Tool name in PascalCase (e.g. "WebSearch"). Used by register_tool.',
        },
        tool_file: {
          type: 'string',
          description: 'Tool file name (e.g. "web-search.js"). Used by register_tool.',
        },
        factory_fn: {
          type: 'string',
          description: 'Factory function name exported from tool file (e.g. "createWebSearchTool"). Used by register_tool.',
        },
        factory_args: {
          type: 'string',
          description: 'Args for factory call (e.g. "config"). Default: "config". Used by register_tool.',
        },
        workflow_name: {
          type: 'string',
          description: 'Workflow file name without .json (e.g. "my-workflow"). Used by add_workflow.',
        },
        workflow_json: {
          type: 'object',
          description: 'Workflow JSON object (VL Workflow Spec 3.16). Used by add_workflow.',
        },
      },
      required: ['action'],
    },

    execute: async (input) => {
      const { action } = input;

      // ── read_source ──
      if (action === 'read_source') {
        if (!input.path) return { error: 'path is required' };
        // Try writable dir first (may have user patches), fall back to install dir
        const candidates = [
          path.join(writableSrcDir, input.path),
          path.join(installDir, input.path),
        ];
        for (const fp of candidates) {
          if (fsSync.existsSync(fp)) {
            try {
              const content = await fs.readFile(fp, 'utf-8');
              return { result: content, lines: content.split('\n').length, path: fp };
            } catch {}
          }
        }
        return { error: `File not found: ${input.path}` };
      }

      // ── write_source ──
      if (action === 'write_source') {
        if (!input.path || input.content === undefined) return { error: 'path and content are required' };

        // In packaged mode, tool files go to ~/.vl-code/tools/ (never reset by updates)
        // Other src files go to app.asar.unpacked (writable, but reset on update — acceptable for fixes)
        let targetDir = writableSrcDir;
        let finalPath = input.path;
        if (IS_PACKAGED && (input.path.startsWith('src/tools/') || input.path.startsWith('tools/'))) {
          const fileName = path.basename(input.path);
          await fs.mkdir(USER_TOOLS_DIR, { recursive: true });
          const fp = path.join(USER_TOOLS_DIR, fileName);
          const existed = fsSync.existsSync(fp);
          await fs.writeFile(fp, input.content, 'utf-8');
          return {
            result: `${existed ? 'Overwrote' : 'Created'} user tool: ${fp}`,
            path: fp,
            note: 'Stored in ~/.vl-code/tools/ — persists across app updates. Use register_tool to activate.',
          };
        }

        const fp = path.join(targetDir, finalPath);
        try {
          await fs.mkdir(path.dirname(fp), { recursive: true });
          const existed = fsSync.existsSync(fp);
          await fs.writeFile(fp, input.content, 'utf-8');
          return { result: `${existed ? 'Overwrote' : 'Created'} ${input.path}`, path: fp };
        } catch (e) {
          if (e.code === 'EACCES' || e.code === 'EROFS') {
            return { error: `Cannot write ${input.path}: file is inside read-only asar. Use a path under src/tools/ to write to ~/.vl-code/tools/ instead.` };
          }
          return { error: `Write failed: ${e.message}` };
        }
      }

      // ── edit_source ──
      if (action === 'edit_source') {
        if (!input.path || input.old_string === undefined || input.new_string === undefined)
          return { error: 'path, old_string and new_string are required' };

        // Find the actual writable file location
        const candidates = [
          path.join(writableSrcDir, input.path),
          path.join(installDir, input.path),
        ];
        let fp = null;
        for (const c of candidates) {
          if (fsSync.existsSync(c)) { fp = c; break; }
        }
        if (!fp) return { error: `File not found: ${input.path}` };

        try {
          let content = await fs.readFile(fp, 'utf-8');
          if (!content.includes(input.old_string))
            return { error: `old_string not found in ${input.path}` };
          content = content.replace(input.old_string, input.new_string);
          await fs.writeFile(fp, content, 'utf-8');
          return { result: `Edited ${input.path}`, path: fp };
        } catch (e) {
          if (e.code === 'EACCES' || e.code === 'EROFS') {
            return { error: `Cannot write ${input.path}: inside read-only asar. Consider write_source to create a patched copy in ~/.vl-code/tools/ instead.` };
          }
          return { error: `Edit failed: ${e.message}` };
        }
      }

      // ── list_source ──
      if (action === 'list_source') {
        const pattern = input.path || 'src/**/*.js';
        try {
          const files = await listMatchingFiles(writableSrcDir, pattern, ['node_modules/**', '.git/**']);
          files.sort();
          // Also show user tools if pattern matches
          const userToolFiles = fsSync.existsSync(USER_TOOLS_DIR)
            ? fsSync.readdirSync(USER_TOOLS_DIR).filter(f => f.endsWith('.js')).map(f => `~/.vl-code/tools/${f}`)
            : [];
          return { result: files.join('\n'), count: files.length, userTools: userToolFiles, pattern };
        } catch (e) {
          return { error: `Glob failed: ${e.message}` };
        }
      }

      // ── register_tool ──
      if (action === 'register_tool') {
        const { tool_name, tool_file, factory_fn } = input;
        if (!tool_name || !tool_file || !factory_fn)
          return { error: 'tool_name, tool_file, and factory_fn are required' };
        const args = input.factory_args || 'config';

        // In packaged mode: write to user registry JSON (loaded at startup)
        if (IS_PACKAGED) {
          await fs.mkdir(USER_TOOLS_DIR, { recursive: true });
          let registry = [];
          if (fsSync.existsSync(USER_TOOL_REGISTRY)) {
            try { registry = JSON.parse(fsSync.readFileSync(USER_TOOL_REGISTRY, 'utf-8')); } catch {}
          }
          const existing = registry.find(t => t.tool_name === tool_name);
          if (existing) return { result: `${tool_name} already in user registry.`, already: true };
          registry.push({ tool_name, tool_file: path.join(USER_TOOLS_DIR, tool_file), factory_fn, factory_args: args });
          await fs.writeFile(USER_TOOL_REGISTRY, JSON.stringify(registry, null, 2), 'utf-8');
          return {
            result: `Registered '${tool_name}' in user registry. Call restart_server to activate.`,
            registry_path: USER_TOOL_REGISTRY,
          };
        }

        // Dev mode: edit src/tools/index.js directly
        const indexPath = path.join(writableSrcDir, 'src', 'tools', 'index.js');
        try {
          let src = await fs.readFile(indexPath, 'utf-8');
          const importLine = `import { ${factory_fn} } from './${tool_file}';`;
          if (!src.includes(importLine)) {
            const lastImport = src.lastIndexOf('\nimport ');
            const insertAt = src.indexOf('\n', lastImport + 1);
            src = src.slice(0, insertAt) + '\n' + importLine + src.slice(insertAt);
          }
          const registerLine = `  registry.register('${tool_name}', ${factory_fn}(${args}));`;
          if (!src.includes(registerLine)) {
            const insertBefore = '  // --- VL Intelligence tools';
            if (src.includes(insertBefore)) {
              src = src.replace(insertBefore, `${registerLine}\n\n${insertBefore}`);
            } else {
              const closing = src.lastIndexOf('\n}');
              src = src.slice(0, closing) + '\n' + registerLine + src.slice(closing);
            }
          }
          await fs.writeFile(indexPath, src, 'utf-8');
          return {
            result: `Registered '${tool_name}' in src/tools/index.js. Call restart_server to activate.`,
          };
        } catch (e) {
          return { error: `register_tool failed: ${e.message}` };
        }
      }

      // ── add_workflow ──
      if (action === 'add_workflow') {
        if (!input.workflow_name || !input.workflow_json)
          return { error: 'workflow_name and workflow_json are required' };
        const wfDir = path.join(homeDir, '.vl-code', 'workflows');
        await fs.mkdir(wfDir, { recursive: true });
        const fp = path.join(wfDir, input.workflow_name + '.json');
        await fs.writeFile(fp, JSON.stringify(input.workflow_json, null, 2), 'utf-8');
        return {
          result: `Workflow '${input.workflow_name}' saved. Available immediately (no restart needed).`,
          path: fp,
        };
      }

      // ── restart_server ──
      if (action === 'restart_server') {
        // In Electron: signal main process via IPC (process.send exists in forked child)
        if (process.send) {
          process.send({ type: 'restart' });
          return {
            result: 'Restart signal sent to Electron main process. Server will restart in ~1s.',
            note: 'Reconnect your browser tab after ~3 seconds.',
          };
        }
        // In standalone Node.js server mode: graceful exit + external restart script
        const port = config.port || 3300;
        const startCmd = `node "${installDir}/bin/vlcode-lite.js"`;
        const script = `sleep 1 && lsof -ti:${port} | xargs kill -9 2>/dev/null; sleep 1 && cd "${installDir}" && ${startCmd}`;
        exec(`nohup bash -c '${script}' > /tmp/vlcode-lite-restart.log 2>&1 &`);
        return {
          result: `Server restart scheduled on port ${port}. Reconnect in ~4 seconds.`,
          note: 'Logs at /tmp/vlcode-lite-restart.log',
        };
      }

      return { error: `Unknown action: ${action}` };
    },
  };
}
