// src/tools/file/write.ts
// Copyright (C) 2025 DTP Technologies, LLC
// All Rights Reserved

import fs from "node:fs/promises";
import path from "node:path";

import type { ToolDefinition } from "../../lib/ai-client.js";
import { ChatSessionMode } from "@/models/chat-session.js";
import type { IChatFileOperation } from "../../models/chat-history.js";

import {
  DtpTool,
  ToolMetadata,
  type ToolArguments,
  type ToolContext,
} from "../../lib/tool.js";
import { validateProjectPath } from "../../lib/path-security.js";
import { PROJECT_ROOT } from "../../config/env.js";
import HostMonitorService from "../../services/host-monitor.js";

const BINARY_EXTENSIONS = new Set([
  ".o",
  ".obj",
  ".a",
  ".lib",
  ".so",
  ".dll",
  ".exe",
  ".bin",
  ".png",
  ".jpg",
  ".jpeg",
  ".gif",
  ".bmp",
  ".ico",
  ".webp",
  ".pdf",
  ".zip",
  ".tar",
  ".gz",
  ".bz2",
  ".7z",
  ".wasm",
  ".pyc",
  ".class",
]);

export class FileWriteTool extends DtpTool {
  get name(): string {
    return "FileWriteTool";
  }
  get slug(): string {
    return "file-write";
  }
  get metadata(): ToolMetadata {
    return {
      name: this.definition.function.name || "file_write",
      category: "file",
      tags: ["file", "write", "create", "save"],
      modes: [
        ChatSessionMode.Plan,
        ChatSessionMode.Build,
        ChatSessionMode.Test,
        ChatSessionMode.Ship,
        ChatSessionMode.Develop,
      ],
    };
  }

  public definition: ToolDefinition = {
    type: "function",
    function: {
      name: "file_write",
      description:
        "Create a new file or overwrite an existing file with the given content. Parent directories are created automatically.",
      parameters: {
        type: "object",
        properties: {
          path: {
            type: "string",
            description:
              "Path to the file to create or overwrite (relative or absolute).",
          },
          content: {
            type: "string",
            description: "The content to write to the file.",
          },
        },
        required: ["path", "content"],
      },
    },
  };

  public async execute(
    _context: ToolContext,
    args: ToolArguments,
  ): Promise<string> {
    const filePath = args.path as string | undefined;
    const content = args.content as string | undefined;

    if (!filePath || filePath.trim().length === 0) {
      return this.error("MISSING_PARAMETER", "File path must not be empty.", {
        parameter: "path",
        recoveryHint: "Provide a valid file path.",
      });
    }

    if (content === undefined) {
      return this.error("MISSING_PARAMETER", "Content must not be undefined.", {
        parameter: "content",
        recoveryHint: "Provide the content to write to the file.",
      });
    }

    // Validate path security - prevent path traversal attacks
    const pathValidation = validateProjectPath(filePath, PROJECT_ROOT);
    if (!pathValidation.valid || !pathValidation.resolvedPath) {
      return this.error(
        "SECURITY_VIOLATION",
        pathValidation.error || "Invalid path",
        {
          parameter: "path",
          recoveryHint:
            "Provide a valid relative path within the project directory.",
        },
      );
    }

    const resolvedPath = pathValidation.resolvedPath;

    try {
      const dir = path.dirname(resolvedPath);
      await fs.mkdir(dir, { recursive: true });

      // Capture existing content for diff (if file exists)
      let oldContent: string | undefined;
      let isExisting = false;
      try {
        oldContent = await fs.readFile(resolvedPath, "utf-8");
        isExisting = true;
      } catch {
        // new file - no previous content
      }

      const contentStr = String(content);
      await fs.writeFile(resolvedPath, contentStr, "utf-8");

      const byteCount = Buffer.byteLength(contentStr, "utf-8");
      const created = !isExisting;

      const binary = BINARY_EXTENSIONS.has(
        path.extname(filePath).toLowerCase(),
      );
      const fileOperation: IChatFileOperation = {
        type: "write",
        path: filePath,
        isBinary: binary,
      };
      if (!binary) {
        const newLines = contentStr.split("\n").length;
        const oldLines = oldContent ? oldContent.split("\n").length : 0;
        fileOperation.linesAdded = isExisting
          ? Math.max(0, newLines - oldLines)
          : newLines;
        fileOperation.linesRemoved = isExisting
          ? Math.max(0, oldLines - newLines)
          : 0;
      }

      HostMonitorService.fileOperation(byteCount);
      HostMonitorService.toolCall(byteCount);

      // Build plain text response with metadata header
      const outputLines = [
        `PATH: ${filePath}`,
        `FILE OPERATION: write`,
        `CREATED: ${created ? "true" : "false"}`,
        `BYTES WRITTEN: ${byteCount}`,
        "---",
        `File written: ${filePath} (${byteCount} bytes)`,
      ];

      return outputLines.join("\n");
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : String(error);

      this.log.error("Failed to write file", {
        path: filePath,
        error: errorMessage,
      });

      return this.error(
        "OPERATION_FAILED",
        `Failed to write file: ${errorMessage}`,
      );
    }
  }
}

export default new FileWriteTool();
