// Copyright © 2013-2026 Jonathan Vasquez <jon@xyinn.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.

const std = @import("std");
const Shard = @import("shard.zig");
const Settings = @import("settings.zig");

const PathBuilder = @This();

const log = std.log.scoped(.path_builder);

pub const COMMON_DIR_NAME = "Common";
pub const CORE_DIR_NAME = "Core";
pub const WORLDSTONES_DIR_NAME = "Worldstones";
pub const OVERLAYS_DIR_NAME = "Overlays";
pub const SHARDS_DIR_NAME = "Shards";
pub const SAVES_DIR_NAME = "Saves";
pub const PREFIXES_DIR_NAME = "Prefixes";
pub const BACKUPS_DIR_NAME = "Backups";
pub const SETTINGS_NAME = "Cactus.json";
pub const SETTINGS_OLD_NAME = "Settings.json";

// These value live as long as the 'root_path' argument in init does.
root_path: []const u8,
settings_path: []const u8,
settings_old_path: []const u8,
common_path: []const u8,
core_path: []const u8,
core_path_relative_to_shard: []const u8,
worldstones_path: []const u8,
worldstones_path_relative_to_shard: []const u8,
overlays_path: []const u8,
overlays_path_relative_to_shard: []const u8,
shards_path: []const u8,
saves_path: []const u8,
prefixes_path: []const u8,
backups_path: []const u8,

/// Caller owns memory.
pub fn init(allocator: std.mem.Allocator, root_path: []const u8) !PathBuilder {
    return .{
        .root_path = root_path,
        .settings_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ root_path, SETTINGS_NAME }),
        .settings_old_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ root_path, SETTINGS_OLD_NAME }),
        .common_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ root_path, COMMON_DIR_NAME }),
        .core_path = try std.fmt.allocPrint(allocator, "{s}/{s}/{s}", .{ root_path, COMMON_DIR_NAME, CORE_DIR_NAME }),
        .core_path_relative_to_shard = try std.fmt.allocPrint(allocator, "../../../{s}/{s}", .{ COMMON_DIR_NAME, CORE_DIR_NAME }),
        .worldstones_path = try std.fmt.allocPrint(allocator, "{s}/{s}/{s}", .{ root_path, COMMON_DIR_NAME, WORLDSTONES_DIR_NAME }),
        .worldstones_path_relative_to_shard = try std.fmt.allocPrint(allocator, "../../../{s}/{s}", .{ COMMON_DIR_NAME, WORLDSTONES_DIR_NAME }),
        .overlays_path = try std.fmt.allocPrint(allocator, "{s}/{s}/{s}", .{ root_path, COMMON_DIR_NAME, OVERLAYS_DIR_NAME }),
        .overlays_path_relative_to_shard = try std.fmt.allocPrint(allocator, "../../../{s}/{s}", .{ COMMON_DIR_NAME, OVERLAYS_DIR_NAME }),
        .shards_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ root_path, SHARDS_DIR_NAME }),
        .saves_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ root_path, SAVES_DIR_NAME }),
        .prefixes_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ root_path, PREFIXES_DIR_NAME }),
        .backups_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ root_path, BACKUPS_DIR_NAME }),
    };
}

pub fn deinit(self: PathBuilder, allocator: std.mem.Allocator) void {
    allocator.free(self.settings_path);
    allocator.free(self.settings_old_path);
    allocator.free(self.common_path);
    allocator.free(self.core_path);
    allocator.free(self.core_path_relative_to_shard);
    allocator.free(self.worldstones_path);
    allocator.free(self.worldstones_path_relative_to_shard);
    allocator.free(self.overlays_path);
    allocator.free(self.overlays_path_relative_to_shard);
    allocator.free(self.shards_path);
    allocator.free(self.saves_path);
    allocator.free(self.prefixes_path);
    allocator.free(self.backups_path);
}

pub fn getRootPath(self: PathBuilder) []const u8 {
    return self.root_path;
}

pub fn getSettingsPath(self: PathBuilder) []const u8 {
    return self.settings_path;
}

// TODO: Remove all code for the old Settings.json file eventually.
pub fn getSettingsOldPath(self: PathBuilder) []const u8 {
    return self.settings_old_path;
}

pub fn getCommonPath(self: PathBuilder) []const u8 {
    return self.common_path;
}

pub fn getCorePath(self: PathBuilder) []const u8 {
    return self.core_path;
}

pub fn getCorePathRelativeToShard(self: PathBuilder) []const u8 {
    return self.core_path_relative_to_shard;
}

pub fn getWorldstonesPath(self: PathBuilder) []const u8 {
    return self.worldstones_path;
}

pub fn getWorldstonesPathRelativeToShard(self: PathBuilder) []const u8 {
    return self.worldstones_path_relative_to_shard;
}

pub fn getOverlaysPath(self: PathBuilder) []const u8 {
    return self.overlays_path;
}

pub fn getOverlaysPathRelativeToShard(self: PathBuilder) []const u8 {
    return self.overlays_path_relative_to_shard;
}

pub fn getSavesPath(self: PathBuilder) []const u8 {
    return self.saves_path;
}

pub fn getShardsPath(self: PathBuilder) []const u8 {
    return self.shards_path;
}

pub fn getPrefixesPath(self: PathBuilder) []const u8 {
    return self.prefixes_path;
}

pub fn getBackupsPath(self: PathBuilder) []const u8 {
    return self.backups_path;
}

// Caller owns memory.
pub fn getSinglePath(_: PathBuilder, allocator: std.mem.Allocator, path: []const u8, shard: *const Shard) ![]const u8 {
    return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ path, shard.alias });
}

// Caller owns memory.
pub fn getSingleParentPath(_: PathBuilder, allocator: std.mem.Allocator, path: []const u8, shard: *const Shard) ![]const u8 {
    return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ path, shard.worldstone });
}

// Caller owns memory.
pub fn getShardPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSinglePath(allocator, self.getShardsPath(), shard);
}

// Caller owns memory.
pub fn getShardParentPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSingleParentPath(allocator, self.getShardsPath(), shard);
}

// Caller owns memory.
pub fn getSavePath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSinglePath(allocator, self.getSavesPath(), shard);
}

// Caller owns memory.
pub fn getSaveParentPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSingleParentPath(allocator, self.getSavesPath(), shard);
}

// Caller owns memory.
pub fn getPrefixPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSinglePath(allocator, self.getPrefixesPath(), shard);
}

// Caller owns memory.
pub fn getPrefixParentPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSingleParentPath(allocator, self.getPrefixesPath(), shard);
}

// Caller owns memory.
pub fn getBackupPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSinglePath(allocator, self.getBackupsPath(), shard);
}

// Caller owns memory.
pub fn getBackupParentPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSingleParentPath(allocator, self.getBackupsPath(), shard);
}

// Caller owns memory.
pub fn getWorldstonePath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSingleParentPath(allocator, self.getWorldstonesPath(), shard);
}

// Caller owns memory.
pub fn getWorldstonePathRelativeToShard(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSingleParentPath(allocator, self.getWorldstonesPathRelativeToShard(), shard);
}

// Caller owns memory.
pub fn getOverlayPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSingleParentPath(allocator, self.getOverlaysPath(), shard);
}

// Caller owns memory.
pub fn getOverlayPathRelativeToShard(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    return try self.getSingleParentPath(allocator, self.getOverlaysPathRelativeToShard(), shard);
}

// Caller owns memory.
pub fn getWineFormatSavePath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    // Get save path
    const save_path = try self.getSavePath(allocator, shard);

    // Swap / to \\
    const wine_save_path = try std.mem.replaceOwned(u8, allocator, save_path, "/", "\\\\");

    log.debug("Wine Save Prefix: {s}", .{wine_save_path});

    // Add Z:\\ prefix and final \\ and return
    return try std.fmt.allocPrint(allocator, "Z:{s}\\\\", .{wine_save_path});
}

// Caller owns memory.
pub fn getWineFormatShardPath(self: PathBuilder, allocator: std.mem.Allocator, shard: *const Shard) ![]const u8 {
    // Get shard path
    const shard_path = try self.getShardPath(allocator, shard);

    // Swap / to \\
    const wine_shard_path = try std.mem.replaceOwned(u8, allocator, shard_path, "/", "\\\\");

    log.debug("Wine Shard Prefix: {s}", .{wine_shard_path});

    // Add Z:\\ prefix and final \\ and return
    return try std.fmt.allocPrint(allocator, "Z:{s}\\\\", .{wine_shard_path});
}
