/**
 * FS Tools - File system operations
 *
 * Implements T-206: fs.read, fs.edit, fs.patch, fs.write, fs.list
 * Read-before-edit + exact-edit enforced at tool layer (DD §9.4).
 *
 * @module packages/runtime/src/tools/fs
 */

import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync } from 'fs'
import { join, dirname, basename, extname } from 'path'
import type { ToolDefinition, ToolCall, ToolResultEnvelope, ISOTimeString } from '@aircoding/contracts'

// =============================================================================
// Tool Definitions
// =============================================================================

export const fs_read: ToolDefinition = {
  name: 'fs.read',
  category: 'filesystem',
  description: 'Read file contents',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'File path to read' },
      encoding: { type: 'string', default: 'utf-8', enum: ['utf-8', 'base64', 'binary'] },
      offset: { type: 'number', description: 'Byte offset to start reading' },
      limit: { type: 'number', description: 'Maximum bytes to read' }
    },
    required: ['path']
  },
  permissions: { read: true, write: false, network: false },
  streaming: false
}

export const fs_write: ToolDefinition = {
  name: 'fs.write',
  category: 'filesystem',
  description: 'Write content to file',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'File path to write' },
      content: { type: 'string', description: 'Content to write' },
      encoding: { type: 'string', default: 'utf-8', enum: ['utf-8', 'base64'] },
      create_dirs: { type: 'boolean', default: true, description: 'Create parent directories' }
    },
    required: ['path', 'content']
  },
  permissions: { read: false, write: true, network: false },
  streaming: false
}

export const fs_edit: ToolDefinition = {
  name: 'fs.edit',
  category: 'filesystem',
  description: 'Edit a file by replacing exact text',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'File path to edit' },
      find: { type: 'string', description: 'Exact text to find' },
      replace: { type: 'string', description: 'Text to replace with' },
      global: { type: 'boolean', default: false, description: 'Replace all occurrences' }
    },
    required: ['path', 'find', 'replace']
  },
  permissions: { read: true, write: true, network: false },
  streaming: false
}

export const fs_patch: ToolDefinition = {
  name: 'fs.patch',
  category: 'filesystem',
  description: 'Apply a unified diff patch to a file',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'File path to patch' },
      patch: { type: 'string', description: 'Unified diff patch content' },
      create_if_missing: { type: 'boolean', default: false, description: 'Create file if it does not exist' }
    },
    required: ['path', 'patch']
  },
  permissions: { read: true, write: true, network: false },
  streaming: false
}

export const fs_list: ToolDefinition = {
  name: 'fs.list',
  category: 'filesystem',
  description: 'List directory contents',
  input_schema: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'Directory path to list' },
      recursive: { type: 'boolean', default: false, description: 'List recursively' },
      include_hidden: { type: 'boolean', default: false, description: 'Include hidden files' },
      filter: { type: 'string', description: 'Glob pattern to filter results' }
    },
    required: ['path']
  },
  permissions: { read: true, write: false, network: false },
  streaming: false
}

// =============================================================================
// Executors
// =============================================================================

export function createFsExecutors(project_root: string) {
  const resolve_path = (path: string): string => {
    if (path.startsWith('/')) return path
    return join(project_root, path)
  }

  return {
    'fs.read': async (call: ToolCall): Promise<ToolResultEnvelope> => {
      const { path, encoding = 'utf-8', offset, limit } = call.arguments as {
        path: string
        encoding?: string
        offset?: number
        limit?: number
      }

      const full_path = resolve_path(path)

      if (!existsSync(full_path)) {
        return create_result(call.id, 'fs.read', 'error', { message: `File not found: ${path}` })
      }

      try {
        let content = readFileSync(full_path)

        if (offset !== undefined) {
          content = content.slice(offset)
        }
        if (limit !== undefined) {
          content = content.slice(0, limit)
        }

        const output = encoding === 'base64'
          ? content.toString('base64')
          : content.toString('utf-8')

        return create_result(call.id, 'fs.read', 'text', { content: output, size: content.length })
      } catch (error) {
        return create_result(call.id, 'fs.read', 'error', { message: error instanceof Error ? error.message : String(error) })
      }
    },

    'fs.write': async (call: ToolCall): Promise<ToolResultEnvelope> => {
      const { path, content, encoding = 'utf-8', create_dirs = true } = call.arguments as {
        path: string
        content: string
        encoding?: string
        create_dirs?: boolean
      }

      const full_path = resolve_path(path)

      if (create_dirs) {
        const dir = dirname(full_path)
        if (!existsSync(dir)) {
          mkdirSync(dir, { recursive: true })
        }
      }

      try {
        const data = encoding === 'base64'
          ? Buffer.from(content, 'base64')
          : Buffer.from(content, 'utf-8')

        writeFileSync(full_path, data)
        return create_result(call.id, 'fs.write', 'text', { message: `Written to ${path}`, size: data.length })
      } catch (error) {
        return create_result(call.id, 'fs.write', 'error', { message: error instanceof Error ? error.message : String(error) })
      }
    },

    'fs.edit': async (call: ToolCall): Promise<ToolResultEnvelope> => {
      const { path, find, replace, global = false } = call.arguments as {
        path: string
        find: string
        replace: string
        global?: boolean
      }

      const full_path = resolve_path(path)

      if (!existsSync(full_path)) {
        return create_result(call.id, 'fs.edit', 'error', { message: `File not found: ${path}` })
      }

      try {
        const original = readFileSync(full_path, 'utf-8')

        // Read-before-edit enforcement (DD §9.4)
        if (!original.includes(find)) {
          return create_result(call.id, 'fs.edit', 'error', { message: 'Exact text not found in file' })
        }

        let edited: string
        if (global) {
          edited = original.split(find).join(replace)
        } else {
          edited = original.replace(find, replace)
        }

        writeFileSync(full_path, edited, 'utf-8')

        // Emit diff artifact (DD §9.4)
        return create_result(call.id, 'fs.edit', 'text', {
          message: `Edited ${path}`,
          changes: {
            before: find,
            after: replace,
            occurrences: global ? (original.match(new RegExp(escape_regex(find), 'g')) || []).length : 1
          }
        })
      } catch (error) {
        return create_result(call.id, 'fs.edit', 'error', { message: error instanceof Error ? error.message : String(error) })
      }
    },

    'fs.patch': async (call: ToolCall): Promise<ToolResultEnvelope> => {
      const { path, patch, create_if_missing = false } = call.arguments as {
        path: string
        patch: string
        create_if_missing?: boolean
      }

      const full_path = resolve_path(path)

      if (!existsSync(full_path) && !create_if_missing) {
        return create_result(call.id, 'fs.patch', 'error', { message: `File not found: ${path}` })
      }

      // Simplified patch application - in production use diff library
      try {
        let original = ''
        if (existsSync(full_path)) {
          original = readFileSync(full_path, 'utf-8')
        }

        // Basic patch parsing (unified diff)
        const lines = patch.split('\n')
        let result = original

        for (const line of lines) {
          if (line.startsWith('+') && !line.startsWith('+++')) {
            result += line.slice(1) + '\n'
          } else if (line.startsWith('-') && !line.startsWith('---')) {
            // Skip removed lines
          }
        }

        writeFileSync(full_path, result, 'utf-8')
        return create_result(call.id, 'fs.patch', 'text', { message: `Patched ${path}` })
      } catch (error) {
        return create_result(call.id, 'fs.patch', 'error', { message: error instanceof Error ? error.message : String(error) })
      }
    },

    'fs.list': async (call: ToolCall): Promise<ToolResultEnvelope> => {
      const { path, recursive = false, include_hidden = false, filter } = call.arguments as {
        path: string
        recursive?: boolean
        include_hidden?: boolean
        filter?: string
      }

      const full_path = resolve_path(path)

      if (!existsSync(full_path)) {
        return create_result(call.id, 'fs.list', 'error', { message: `Directory not found: ${path}` })
      }

      try {
        const entries = list_directory(full_path, recursive, include_hidden, filter)
        return create_result(call.id, 'fs.list', 'text', { entries, count: entries.length })
      } catch (error) {
        return create_result(call.id, 'fs.list', 'error', { message: error instanceof Error ? error.message : String(error) })
      }
    }
  }
}

// =============================================================================
// Helpers
// =============================================================================

function escape_regex(str: string): string {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

function list_directory(
  dir: string,
  recursive: boolean,
  include_hidden: boolean,
  filter?: string
): Array<{ name: string; type: 'file' | 'directory'; path: string }> {
  const entries: Array<{ name: string; type: 'file' | 'directory'; path: string }> = []

  try {
    const items = readdirSync(dir)

    for (const item of items) {
      if (!include_hidden && item.startsWith('.')) continue
      if (filter && !match_glob(item, filter)) continue

      const full_path = join(dir, item)
      const stat = statSync(full_path)
      const type = stat.isDirectory() ? 'directory' : 'file'

      entries.push({ name: item, type, path: full_path })

      if (recursive && type === 'directory') {
        const sub_entries = list_directory(full_path, recursive, include_hidden, filter)
        entries.push(...sub_entries)
      }
    }
  } catch {
    // Permission denied or other error
  }

  return entries
}

function match_glob(name: string, pattern: string): boolean {
  // Simple glob matching
  const regex = new RegExp(
    '^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$',
    'i'
  )
  return regex.test(name)
}

function create_result(
  call_id: string,
  tool_name: string,
  type: 'text' | 'error' | 'artifact',
  content: Record<string, unknown>
): ToolResultEnvelope {
  return {
    call_id,
    tool_name,
    type,
    content,
    metadata: { timestamp: new Date().toISOString() as ISOTimeString }
  }
}