// SPDX-FileCopyrightText: 2021-2023 Yoran Heling <projects@yorhel.nl>
// SPDX-License-Identifier: MIT

const std = @import("std");
const main = @import("main.zig");
const model = @import("model.zig");
const ui = @import("ui.zig");
const browser = @import("browser.zig");
const util = @import("util.zig");

var parent: *model.Dir = undefined;
var entry: *model.Entry = undefined;
var next_sel: ?*model.Entry = undefined; // Which item to select if deletion succeeds
var state: enum { confirm, busy, err } = .confirm;
var confirm: enum { yes, no, ignore } = .no;
var error_option: enum { abort, ignore, all } = .abort;
var error_code: anyerror = undefined;

pub fn setup(p: *model.Dir, e: *model.Entry, n: ?*model.Entry) void {
    parent = p;
    entry = e;
    next_sel = n;
    state = if (main.config.confirm_delete) .confirm else .busy;
    confirm = .no;
}


// Returns true to abort scanning.
fn err(e: anyerror) bool {
    if (main.config.ignore_delete_errors)
        return false;
    error_code = e;
    state = .err;

    while (main.state == .delete and state == .err)
        main.handleEvent(true, false);

    return main.state != .delete;
}

fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool {
    entry = ptr.*.?;
    main.handleEvent(false, false);
    if (main.state != .delete)
        return true;

    if (entry.dir()) |d| {
        var fd = dir.openDirZ(path, .{.no_follow = true}, false) catch |e| return err(e);
        var it = &d.sub;
        parent = d;
        defer parent = parent.parent.?;
        while (it.*) |n| {
            if (deleteItem(fd, n.name(), it)) {
                fd.close();
                return true;
            }
            if (it.* == n) // item deletion failed, make sure to still advance to next
                it = &n.next;
        }
        fd.close();
        dir.deleteDirZ(path) catch |e|
            return if (e != error.DirNotEmpty or d.sub == null) err(e) else false;
    } else
        dir.deleteFileZ(path) catch |e| return err(e);
    ptr.*.?.delStats(parent);
    ptr.* = ptr.*.?.next;
    return false;
}

// Returns the item that should be selected in the browser.
pub fn delete() ?*model.Entry {
    while (main.state == .delete and state == .confirm)
        main.handleEvent(true, false);
    if (main.state != .delete)
        return entry;

    // Find the pointer to this entry
    const e = entry;
    var it = &parent.sub;
    while (it.*) |n| : (it = &n.next)
        if (it.* == entry)
            break;

    var path = std.ArrayList(u8).init(main.allocator);
    defer path.deinit();
    parent.fmtPath(true, &path);
    if (path.items.len == 0 or path.items[path.items.len-1] != '/')
        path.append('/') catch unreachable;
    path.appendSlice(entry.name()) catch unreachable;

    _ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it);
    model.inodes.addAllStats();
    return if (it.* == e) e else next_sel;
}

fn drawConfirm() void {
    browser.draw();
    const box = ui.Box.create(6, 60, "Confirm delete");
    box.move(1, 2);
    ui.addstr("Are you sure you want to delete \"");
    ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 21));
    ui.addch('"');
    if (entry.pack.etype != .dir)
        ui.addch('?')
    else {
        box.move(2, 18);
        ui.addstr("and all of its contents?");
    }

    box.move(4, 15);
    ui.style(if (confirm == .yes) .sel else .default);
    ui.addstr("yes");

    box.move(4, 25);
    ui.style(if (confirm == .no) .sel else .default);
    ui.addstr("no");

    box.move(4, 31);
    ui.style(if (confirm == .ignore) .sel else .default);
    ui.addstr("don't ask me again");
}

fn drawProgress() void {
    var path = std.ArrayList(u8).init(main.allocator);
    defer path.deinit();
    parent.fmtPath(false, &path);
    path.append('/') catch unreachable;
    path.appendSlice(entry.name()) catch unreachable;

    // TODO: Item counts and progress bar would be nice.

    const box = ui.Box.create(6, 60, "Deleting...");
    box.move(2, 2);
    ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 56));
    box.move(4, 41);
    ui.addstr("Press ");
    ui.style(.key);
    ui.addch('q');
    ui.style(.default);
    ui.addstr(" to abort");
}

fn drawErr() void {
    var path = std.ArrayList(u8).init(main.allocator);
    defer path.deinit();
    parent.fmtPath(false, &path);
    path.append('/') catch unreachable;
    path.appendSlice(entry.name()) catch unreachable;

    const box = ui.Box.create(6, 60, "Error");
    box.move(1, 2);
    ui.addstr("Error deleting ");
    ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 41));
    box.move(2, 4);
    ui.addstr(ui.errorString(error_code));

    box.move(4, 14);
    ui.style(if (error_option == .abort) .sel else .default);
    ui.addstr("abort");

    box.move(4, 23);
    ui.style(if (error_option == .ignore) .sel else .default);
    ui.addstr("ignore");

    box.move(4, 33);
    ui.style(if (error_option == .all) .sel else .default);
    ui.addstr("ignore all");
}

pub fn draw() void {
    switch (state) {
        .confirm => drawConfirm(),
        .busy => drawProgress(),
        .err => drawErr(),
    }
}

pub fn keyInput(ch: i32) void {
    switch (state) {
        .confirm => switch (ch) {
            'h', ui.c.KEY_LEFT => confirm = switch (confirm) {
                .ignore => .no,
                else => .yes,
            },
            'l', ui.c.KEY_RIGHT => confirm = switch (confirm) {
                .yes => .no,
                else => .ignore,
            },
            'q' => main.state = .browse,
            '\n' => switch (confirm) {
                .yes => state = .busy,
                .no => main.state = .browse,
                .ignore => {
                    main.config.confirm_delete = false;
                    state = .busy;
                },
            },
            else => {}
        },
        .busy => {
            if (ch == 'q')
                main.state = .browse;
        },
        .err => switch (ch) {
            'h', ui.c.KEY_LEFT => error_option = switch (error_option) {
                .all => .ignore,
                else => .abort,
            },
            'l', ui.c.KEY_RIGHT => error_option = switch (error_option) {
                .abort => .ignore,
                else => .all,
            },
            'q' => main.state = .browse,
            '\n' => switch (error_option) {
                .abort => main.state = .browse,
                .ignore => state = .busy,
                .all => {
                    main.config.ignore_delete_errors = true;
                    state = .busy;
                },
            },
            else => {}
        },
    }
}