const std = @import("std");
const os = std.os;
const log = std.log;

/// File watcher using OS-native APIs (kqueue on macOS, inotify on Linux)
/// Zero third-party dependencies - uses only standard library OS bindings
pub const FileWatcher = struct {
    allocator: std.mem.Allocator,
    watched_paths: std.StringHashMap(void),

    // Platform-specific handles
    kqueue_fd: if (@import("builtin").target.os.tag == .macos) i32 else void,
    inotify_fd: if (@import("builtin").target.os.tag == .linux) i32 else void,

    // Debounce timer
    last_event_time: i64,
    debounce_ms: i64,

    pub fn init(allocator: std.mem.Allocator, debounce_ms: i64) !FileWatcher {
        const target = @import("builtin").target;

        var watcher = FileWatcher{
            .allocator = allocator,
            .watched_paths = std.StringHashMap(void).init(allocator),
            .kqueue_fd = if (target.os.tag == .macos) -1 else {},
            .inotify_fd = if (target.os.tag == .linux) -1 else {},
            .last_event_time = 0,
            .debounce_ms = debounce_ms,
        };

        switch (target.os.tag) {
            .macos => {
                watcher.kqueue_fd = try os.kqueue();
            },
            .linux => {
                watcher.inotify_fd = try std.os.inotify_init1(os.linux.IN_CLOEXEC);
            },
            else => {
                return error.UnsupportedPlatform;
            },
        }

        return watcher;
    }

    pub fn deinit(self: *FileWatcher) void {
        const target = @import("builtin").target;

        switch (target.os.tag) {
            .macos => {
                if (self.kqueue_fd != -1) {
                    os.close(self.kqueue_fd);
                }
            },
            .linux => {
                if (self.inotify_fd != -1) {
                    os.close(self.inotify_fd);
                }
            },
            else => {},
        }

        var it = self.watched_paths.keyIterator();
        while (it.next()) |key| {
            self.allocator.free(key.*);
        }
        self.watched_paths.deinit();
    }

    /// Add a directory to watch recursively
    pub fn watchDirectory(self: *FileWatcher, path: []const u8) !void {
        if (self.watched_paths.contains(path)) return;

        const path_copy = try self.allocator.dupe(u8, path);
        try self.watched_paths.put(path_copy, {});

        const target = @import("builtin").target;

        switch (target.os.tag) {
            .macos => try self.addKqueueWatch(path),
            .linux => try self.addInotifyWatch(path),
            else => return error.UnsupportedPlatform,
        }

        // Recursively watch subdirectories
        var dir = std.fs.cwd().openDir(path, .{ .iterate = true }) catch |err| switch (err) {
            error.AccessDenied => return,
            error.FileNotFound => return,
            else => return err,
        };
        defer dir.close();

        var it = dir.iterate();
        while (true) {
            const entry = it.next() catch |err| switch (err) {
                error.AccessDenied => continue,
                else => return err,
            } orelse break;

            if (entry.kind == .directory) {
                const subpath = try std.fs.path.join(self.allocator, &[_][]const u8{ path, entry.name });
                defer self.allocator.free(subpath);
                try self.watchDirectory(subpath);
            }
        }
    }

    /// Add kqueue watch for macOS
    fn addKqueueWatch(self: *FileWatcher, path: []const u8) !void {
        if (@import("builtin").target.os.tag != .macos) return;

        const fd = try os.open(path, os.O.EVTONLY | os.O_RDONLY, 0);
        defer os.close(fd);

        const event = os.Kevent{
            .ident = @intCast(fd),
            .filter = os.EVFILT_VNODE,
            .flags = os.EV_ADD | os.EV_CLEAR,
            .fflags = os.NOTE_WRITE | os.NOTE_EXTEND | os.NOTE_ATTRIB | os.NOTE_LINK | os.NOTE_RENAME | os.NOTE_REVOKE,
            .data = 0,
            .udata = 0,
        };

        const changes = [_]os.Kevent{event};
        _ = try os.kevent(self.kqueue_fd, &changes, &.{}, null);
    }

    /// Add inotify watch for Linux
    fn addInotifyWatch(self: *FileWatcher, path: []const u8) !void {
        if (@import("builtin").target.os.tag != .linux) return;

        const mask = os.linux.IN_MODIFY | os.linux.IN_CREATE | os.linux.IN_DELETE |
            os.linux.IN_MOVED_FROM | os.linux.IN_MOVED_TO | os.linux.IN_ATTRIB;

        const wd = try os.linux.inotify_add_watch(self.inotify_fd, path.ptr, mask);
        if (wd < 0) return error.InotifyError;
    }

    /// Wait for file changes with debouncing
    pub fn waitForChanges(self: *FileWatcher, timeout_ms: i32) !bool {
        const target = @import("builtin").target;

        switch (target.os.tag) {
            .macos => return try self.waitKqueue(timeout_ms),
            .linux => return try self.waitInotify(timeout_ms),
            else => return error.UnsupportedPlatform,
        }
    }

    /// kqueue wait implementation
    fn waitKqueue(self: *FileWatcher, timeout_ms: i32) !bool {
        if (@import("builtin").target.os.tag != .macos) return false;

        var ts: os.timespec = undefined;
        if (timeout_ms >= 0) {
            ts.tv_sec = @divTrunc(timeout_ms, 1000);
            ts.tv_nsec = @mod(timeout_ms, 1000) * 1000000;
        }

        var events: [10]os.Kevent = undefined;
        const nev = os.kevent(self.kqueue_fd, &.{}, &events, if (timeout_ms >= 0) &ts else null) catch |err| switch (err) {
            error.ETIME => return false, // Timeout
            else => return err,
        };

        if (nev > 0) {
            const now = std.time.milliTimestamp();
            if (now - self.last_event_time > self.debounce_ms) {
                self.last_event_time = now;
                return true;
            }
        }

        return false;
    }

    /// inotify wait implementation
    fn waitInotify(self: *FileWatcher, timeout_ms: i32) !bool {
        if (@import("builtin").target.os.tag != .linux) return false;

        var fds = [_]os.pollfd{.{
            .fd = self.inotify_fd,
            .events = os.POLLIN,
            .revents = 0,
        }};

        const ready = os.poll(&fds, timeout_ms) catch |err| switch (err) {
            error.ETIME => return false,
            else => return err,
        };

        if (ready > 0 and (fds[0].revents & os.POLLIN) != 0) {
            var buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;

            const bytes_read = try os.read(self.inotify_fd, &buf);
            if (bytes_read > 0) {
                const now = std.time.milliTimestamp();
                if (now - self.last_event_time > self.debounce_ms) {
                    self.last_event_time = now;
                    return true;
                }
            }
        }

        return false;
    }

    /// Run watch loop with callback
    pub fn run(self: *FileWatcher, callback: fn () void) !void {
        log.info("Watching for file changes (debounce: {d}ms)...", .{self.debounce_ms});

        while (true) {
            if (try self.waitForChanges(-1)) {
                callback();
            }
        }
    }
};

/// Watch command handler
pub fn watchCommand(allocator: std.mem.Allocator, args: []const []const u8) !void {
    if (args.len < 1) {
        std.debug.print("Usage: ml watch <path> [--sync] [--queue]\n", .{});
        return error.InvalidArgs;
    }

    const path = args[0];
    var auto_sync = false;
    var auto_queue = false;

    // Parse flags
    for (args[1..]) |arg| {
        if (std.mem.eql(u8, arg, "--sync")) auto_sync = true;
        if (std.mem.eql(u8, arg, "--queue")) auto_queue = true;
    }

    var watcher = try FileWatcher.init(allocator, 100); // 100ms debounce
    defer watcher.deinit();

    try watcher.watchDirectory(path);

    log.info("Watching {s} for changes...", .{path});

    // Callback for file changes
    const CallbackContext = struct {
        allocator: std.mem.Allocator,
        path: []const u8,
        auto_sync: bool,
        auto_queue: bool,
    };

    const ctx = CallbackContext{
        .allocator = allocator,
        .path = path,
        .auto_sync = auto_sync,
        .auto_queue = auto_queue,
    };

    // Run watch loop
    while (true) {
        if (try watcher.waitForChanges(-1)) {
            log.info("File changes detected", .{});

            if (auto_sync) {
                log.info("Auto-syncing...", .{});
                // Trigger sync (implementation would call sync command)
                _ = ctx;
            }

            if (auto_queue) {
                log.info("Auto-queuing...", .{});
                // Trigger queue (implementation would call queue command)
            }
        }
    }
}

test "FileWatcher init/deinit" {
    const allocator = std.testing.allocator;

    var watcher = try FileWatcher.init(allocator, 100);
    defer watcher.deinit();
}
