const std = @import("std");
const Api = @import("artifacts-api");
const Position = Api.Position;
const Server = Api.Server;
const assert = std.debug.assert;

pub const Action = union(enum) {
    move: Position,
    fight,
    gather,
    deposit_gold: u64,
    deposit_item: Api.ItemQuantity,
    withdraw_item: Api.ItemQuantity,
    craft_item: Api.ItemQuantity,
    accept_task,

    pub fn perform(self: Action, api: *Server, name: []const u8) !ActionResult {
        const log = std.log.default;

        switch (self) {
            .fight => {
                log.debug("[{s}] attack", .{name});
                return .{
                    .fight = api.actionFight(name)
                };
            },
            .move => |pos| {
                log.debug("[{s}] move to ({}, {})", .{name, pos.x, pos.y});
                return .{
                    .move = api.actionMove(name, pos.x, pos.y)
                };
            },
            .deposit_gold => |quantity| {
                log.debug("[{s}] deposit {} gold", .{name, quantity});
                return .{
                    .deposit_gold = api.actionBankDepositGold(name, quantity)
                };
            },
            .deposit_item => |item| {
                const code = api.store.getCode(item.id) orelse return error.ItemNotFound;
                log.debug("[{s}] deposit {s} (x{})", .{name, code, item.quantity});
                return .{
                    .deposit_item = api.actionBankDepositItem(name, code, item.quantity)
                };
            },
            .withdraw_item => |item| {
                const code = api.store.getCode(item.id) orelse return error.ItemNotFound;
                log.debug("[{s}] withdraw {s} (x{})", .{name, code, item.quantity});
                return .{
                    .withdraw_item = api.actionBankWithdrawItem(name, code, item.quantity)
                };
            },
            .gather => {
                log.debug("[{s}] gather", .{name});
                return .{
                    .gather = api.actionGather(name)
                };
            },
            .craft_item => |item| {
                const code = api.store.getCode(item.id) orelse return error.ItemNotFound;
                log.debug("[{s}] craft {s} (x{})", .{name, code, item.quantity});
                return .{
                    .craft_item = api.actionCraft(name, code, item.quantity)
                };
            },
            .accept_task => {
                log.debug("[{s}] accept task", .{name});
                return .{
                    .accept_task = api.acceptTask(name)
                };
            }
        }
    }
};

pub const ErrorResponse = enum {
    /// Something went wrong, and you probably can't reasonbly recover from it. Bail, bail!
    abort,

    /// You probably were trying to an action a bit too early, just try again a bit later.
    retry,

    /// Something in your logic went wrong, re-evaluate your state and do something different.
    restart,

    /// The error can be safe ignored, continue doing the next action that you wanted.
    ignore
};

pub const ActionResult = union(enum) {
    move: Api.MoveError!Server.MoveResult,
    fight: Api.FightError!Server.FightResult,
    gather: Api.GatherError!Server.GatherResult,
    deposit_gold: Api.BankDepositGoldError!Server.GoldTransactionResult,
    deposit_item: Api.BankDepositItemError!Server.ItemTransactionResult,
    withdraw_item: Api.BankWithdrawItemError!Server.ItemTransactionResult,
    craft_item: Api.CraftError!Server.CraftResult,
    accept_task: Api.AcceptTaskError!Server.AcceptTaskResult,

    const AnyError = Server.MoveError;

    const Tag = @typeInfo(ActionResult).Union.tag_type.?;

    fn fieldType(comptime kind: Tag) type {
        const field_type = std.meta.fields(ActionResult)[@intFromEnum(kind)].type;
        return @typeInfo(field_type).ErrorUnion.payload;
    }

    pub fn get(self: ActionResult, comptime kind: Tag) ?fieldType(kind) {
        return switch (self) {
            kind => |v| v catch null,
            else => null
        };
    }

    pub fn getError(self: ActionResult) !void {
        switch (self) {
            .fight => |result| {
                _ = try result;
            },
            .move => |result| {
                _ = try result;
            },
            .deposit_gold => |result| {
                _ = try result;
            },
            .deposit_item => |result| {
                _ = try result;
            },
            .withdraw_item => |result| {
                _ = try result;
            },
            .gather => |result| {
                _ = try result;
            },
            .craft_item => |result| {
                _ = try result;
            },
            .accept_task => |result| {
                _ = try result;
            }
        }
    }

    pub fn getErrorResponse(self: ActionResult) ?ErrorResponse {
        self.getError() catch |err| switch (err) {
            error.CharacterIsBusy,
            error.CharacterInCooldown,
            error.BankIsBusy => return ErrorResponse.retry,

            error.CharacterAtDestination => return ErrorResponse.ignore,

            error.MapNotFound,
            error.CharacterIsFull,
            error.MonsterNotFound,
            error.NotEnoughSkill,
            error.ResourceNotFound,
            error.NotEnoughGold,
            error.BankNotFound,
            error.ItemNotFound,
            error.NotEnoughItems,
            error.RecipeNotFound,
            error.AlreadyHasTask,
            error.TaskMasterNotFound,
            error.WorkshopNotFound => return ErrorResponse.restart,

            error.CharacterNotFound,
            error.ServerUnavailable,
            error.RequestFailed,
            error.ParseFailed,
            error.OutOfMemory => return ErrorResponse.abort
        };

        return null;
    }
};

comptime {
    const ActionTag = @typeInfo(Action).Union.tag_type.?;
    const ResultTag = @typeInfo(ActionResult).Union.tag_type.?;

    assert(std.meta.fields(ActionTag).len == std.meta.fields(ResultTag).len);
}
