import io
import textwrap
import zipfile
from dataclasses import dataclass
from typing import Optional, Any
from zipfile import ZipFile, ZIP_DEFLATED

import yaml

from Class.openkhmod import (
    AttackEntriesOrganizer,
    ModYml,
    Bonuses,
    FormLevels,
    Items,
    LevelUps,
    Messages,
    PlayerParams,
    PrizeTable,
    Treasures,
    write_yaml_to_zip_file,
    Asset,
)
from Module.RandomizerSettings import RandomizerSettings
from Module.resources import resource_path


def _relative_mod_file(name: str) -> str:
    """Used to help organize mod-specific files inside the mod zip."""
    return f"randoseed-mod-files/{name}"


@dataclass(frozen=True)
class ChestVisualAssignment:
    chest_index: int
    spawn_file_path: str
    chest_visual_id: int


class SeedModBuilder:
    def __init__(self, title: str, out_zip: ZipFile):
        self.out_zip = out_zip
        self.mod_yml = ModYml(
            title, description="Generated by the KH2 Randomizer Seed Generator."
        )
        self.bonuses = Bonuses(_relative_mod_file("BonsList.yml"))
        self.form_levels = FormLevels(_relative_mod_file("FmlvList.yml"))
        self.items = Items(_relative_mod_file("ItemList.yml"))
        self.level_ups = LevelUps(_relative_mod_file("LvupList.yml"))
        self.messages = Messages(_relative_mod_file("sys.yml"))
        self.journal_txt = Messages(_relative_mod_file("jm.yml"), unicode_output=True)
        self.player_params = PlayerParams(_relative_mod_file("PlrpList.yml"))
        self.prize_table = PrizeTable(_relative_mod_file("przt.yml"))
        self.treasures = Treasures(_relative_mod_file("TrsrList.yml"))
        self.atkp_organizer = AttackEntriesOrganizer(_relative_mod_file("AtkpList.yml"))

    def add_base_assets(self):
        """Adds asset entries to the mod for files that get included with every seed."""
        self.mod_yml.add_assets(
            [
                {
                    "name": "msg/us/sys.bar",
                    "multi": [
                        {"name": "msg/fr/sys.bar"},
                        {"name": "msg/gr/sys.bar"},
                        {"name": "msg/it/sys.bar"},
                        {"name": "msg/sp/sys.bar"},
                    ],
                    "method": "binarc",
                    "source": [
                        {
                            "name": "sys",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [
                                {"name": self.messages.source_name, "language": "en"}
                            ],
                        }
                    ],
                },
                {
                    "name": "msg/jp/sys.bar",
                    "platform": "ps2",
                    "method": "binarc",
                    "source": [
                        {
                            "name": "sys",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [
                                {"name": self.messages.source_name, "language": "en"}
                            ],
                        }
                    ],
                },
                {
                    "name": "msg/jp/sys.bar",
                    "platform": "pc",
                    "method": "binarc",
                    "source": [
                        {
                            "name": "sys",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [
                                {"name": self.messages.source_name, "language": "jp"}
                            ],
                        }
                    ],
                },
                {
                    "name": "msg/us/jm.bar",
                    "multi": [
                        {"name": "msg/fr/jm.bar"},
                        {"name": "msg/gr/jm.bar"},
                        {"name": "msg/it/jm.bar"},
                        {"name": "msg/sp/jm.bar"},
                    ],
                    "method": "binarc",
                    "source": [
                        {
                            "name": "jm",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [
                                {"name": self.journal_txt.source_name, "language": "en"}
                            ],
                        }
                    ],
                },
                {
                    "name": "msg/jp/jm.bar",
                    "platform": "ps2",
                    "method": "binarc",
                    "source": [
                        {
                            "name": "jm",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [
                                {"name": self.journal_txt.source_name, "language": "en"}
                            ],
                        }
                    ],
                },
                {
                    "name": "msg/jp/jm.bar",
                    "platform": "pc",
                    "method": "binarc",
                    "source": [
                        {
                            "name": "jm",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [
                                {"name": self.journal_txt.source_name, "language": "jp"}
                            ],
                        }
                    ],
                },
                {
                    "name": "00battle.bin",
                    "method": "binarc",
                    "source": [
                        {
                            "name": "fmlv",
                            "method": "listpatch",
                            "type": "List",
                            "source": [
                                {"name": self.form_levels.source_name, "type": "fmlv"}
                            ],
                        },
                        {
                            "name": "lvup",
                            "method": "listpatch",
                            "type": "List",
                            "source": [
                                {"name": self.level_ups.source_name, "type": "lvup"}
                            ],
                        },
                        {
                            "name": "bons",
                            "method": "listpatch",
                            "type": "List",
                            "source": [
                                {"name": self.bonuses.source_name, "type": "bons"}
                            ],
                        },
                        {
                            "name": "plrp",
                            "method": "listpatch",
                            "type": "List",
                            "source": [
                                {"name": self.player_params.source_name, "type": "plrp"}
                            ],
                        },
                    ],
                },
                {
                    "name": "03system.bin",
                    "method": "binarc",
                    "source": [
                        {
                            "name": "trsr",
                            "method": "listpatch",
                            "type": "List",
                            "source": [
                                {"name": self.treasures.source_name, "type": "trsr"}
                            ],
                        },
                        {
                            "name": "item",
                            "method": "listpatch",
                            "type": "List",
                            "source": [
                                {"name": self.items.source_name, "type": "item"}
                            ],
                        },
                    ],
                },
            ]
        )
    def add_emblem_randomization_mods(self, num_emblems_needed):
        self.messages.add_message(
            15115,
            en="Lucky Emblem",
            jp="Lucky Emblem", # TODO make actual text for this
        )
        self.messages.add_message(
            15116,
            en=f"A Lucky Emblem.\nThe King left these for you to find.\n{num_emblems_needed} of these are required to open the door...",
            jp=f"A Lucky Emblem.\nThe King left these for you to find.\n{num_emblems_needed} of these are required to open the door...", # TODO make actual text for this
        )
        mark_image_source = _relative_mod_file("objectives/completionmark.dds")
        emu_mark_image_source = _relative_mod_file("objectives/completionmark.imd")
        self.mod_yml.add_asset({
            "name": "remastered/itempic/item-226.imd/-0.dds",
            "method": "copy",
            "platform": "pc",
            "source": [
                {
                    "name": mark_image_source,
                }
            ]
        })
        self.mod_yml.add_asset({
            "name": "itempic/item-226.imd",
            "method": "copy",
            "platform": "ps2",
            "source": [
                {
                    "name": emu_mark_image_source,
                }
            ]
        })
        self.out_zip.write(
            resource_path("static/objectives/completionmark.dds"), mark_image_source
        )
        self.out_zip.write(
            resource_path("static/objectives/completionmark.imd"), emu_mark_image_source
        )


    def add_objective_randomization_mods(self, num_objectives_needed, objective_list):
        def convert_string_to_unicode(string: str, newlines: int = 0):
            return (
                    "".join(r"\u{:04X}".format(ord(character)) for character in textwrap.fill(string, width=30))
                    + "NEWLINE" * newlines
            )
        self.messages.add_message(
            15115,
            en="Completion Mark",
            jp="Completion Mark", # TODO make actual text for this
        )
        self.messages.add_message(
            15116,
            en=f"An Objective Completion Mark.\nAwarded to those who complete given tasks.\n{num_objectives_needed} of these are required to open the door...",
            jp=f"An Objective Completion Mark.\nAwarded to those who complete given tasks.\n{num_objectives_needed} of these are required to open the door...", # TODO make actual text for this
        )
        self.journal_txt.add_message(
            14260,
            en=convert_string_to_unicode("Objective List")
        )       
        self.journal_txt.add_message(
            13945,
            en=convert_string_to_unicode("Reports/Objectives")
        )     
        listText = ''
        counter = 1
        for task in objective_list:
            listText += convert_string_to_unicode(str(counter)+'. '+task.Name,1)
            counter+=1

        self.journal_txt.add_message(
            message_id=14261,
            en=listText
        )
        
        mark_image_source = _relative_mod_file("objectives/completionmark.dds")
        emu_mark_image_source = _relative_mod_file("objectives/completionmark.imd")
        report_addition_bin = _relative_mod_file("objectives/ansem_modified.bin")

        self.mod_yml.add_asset({
            "name": "remastered/itempic/item-226.imd/-0.dds",
            "method": "copy",
            "platform": "pc",
            "source": [
                {
                    "name": mark_image_source,
                }
            ]
        })
        self.mod_yml.add_asset({
            "name": "itempic/item-226.imd",
            "method": "copy",
            "platform": "ps2",
            "source": [
                {
                    "name": emu_mark_image_source,
                }
            ]
        })

        source_data = {
                    "name": "anse",
                    "method":"copy",
                    "type":"jimidata",
                    "source" : [{"name":report_addition_bin}]
                }

        if self.mod_yml.find_asset("menu/fm/jiminy.bar") is not None:
            self.mod_yml.add_asset_source("menu/fm/jiminy.bar",source_data)
            self.mod_yml.add_asset_source("menu/us/jiminy.bar",source_data)
        else:
            self.mod_yml.add_asset(
                {"name": "menu/fm/jiminy.bar", "method": "binarc", "source": [source_data]}
            )
            self.mod_yml.add_asset(
                {
                    "name": "menu/us/jiminy.bar",
                    "platform": "pc",
                    "multi": [
                        {"name": "menu/fr/jiminy.bar"},
                        {"name": "menu/gr/jiminy.bar"},
                        {"name": "menu/it/jiminy.bar"},
                        {"name": "menu/sp/jiminy.bar"},
                        {"name": "menu/uk/jiminy.bar"},
                    ],
                    "method": "binarc",
                    "source": [source_data],
                }
            )
        self.out_zip.write(
            resource_path("static/objectives/completionmark.dds"), mark_image_source
        )
        self.out_zip.write(
            resource_path("static/objectives/completionmark.imd"), emu_mark_image_source
        )
        self.out_zip.write(
            resource_path("static/objectives/ansem_modified.bin"), report_addition_bin
        )


    def add_sora_bdscript(self,item_ids,ability_ids,equipped):
        if len(ability_ids)==0 and len(item_ids)==0:
            return
        preamble = "pushImm 1\n" \
        "syscall 0, 61 ; trap_saveram_get_partram (1 in, 1 out)\n" \
        "popToSp 0\n"
        ability_string = "pushFromFSp 0\n"\
        "pushImm ITEM_ID\n"\
        "pushImm EQUIPPED\n"\
        "syscall 0, 99 ; trap_partram_add_ability (3 in, 0 out)\n"
        item_string = "pushImm ITEM_ID\n"\
        "pushImm 100\n"\
        "syscall 0, 63 ; trap_item_get (2 in, 0 out)\n"
        postamble = "ret\n"

        item_script = ""
        if len(ability_ids)!=0:
            item_script+=preamble
            for id in ability_ids:
                item_script+=ability_string.replace("ITEM_ID",str(id)).replace("EQUIPPED",str(int(equipped)))
        if len(item_ids)!=0:
            for id in item_ids:
                item_script+=item_string.replace("ITEM_ID",str(id))
        item_script+=postamble

        with open(resource_path("static/starting/starting_inventory.bdscript"),"r") as infile:
            full_item_script = infile.read()

        full_item_script = full_item_script.replace("ITEM_LIST_GOES_HERE",item_script)
        starting_item_bdscript = _relative_mod_file("starting_items/starting_inventory.bdscript")
        starting_item_evt = _relative_mod_file("starting_items/starting_item_event.script")

        self.mod_yml.add_asset({
            "name": "ard/tt32.ard",
            "method": "binarc",
            "multi": [
                {"name":"ard/us/tt32.ard"},
                {"name":"ard/fr/tt32.ard"},
                {"name":"ard/gr/tt32.ard"},
                {"name":"ard/it/tt32.ard"},
                {"name":"ard/sp/tt32.ard"},
                {"name":"ard/uk/tt32.ard"},
                {"name":"ard/jp/tt32.ard"},
            ],
            "source": [
                {
                    "name": "evt",
                    "method":"areadatascript",
                    "type":"areadatascript",
                    "source" : [{"name":starting_item_evt}]

                },
                {
                    "name": "starting_abilities",
                    "method":"bdscript",
                    "type":"Bdx",
                    "source" : [{"name":starting_item_bdscript}]

                }
            ],
        })
        
        self.out_zip.writestr(starting_item_bdscript, full_item_script)
        self.out_zip.write(
            resource_path("static/starting/starting_item_event.script"), starting_item_evt
        )

    def add_base_messages(
            self,
            seed_hash_icons: list[str],
            crit_mode: bool,
            final_door_requirement_message: str
    ):
        """Adds messages that are included with every seed."""
        seed_hash_string = " ".join(
            ["{:icon " + icon + "}" for icon in seed_hash_icons]
        )
        self.messages.add_message(17198, en=seed_hash_string, jp=seed_hash_string)

        self.messages.add_message(20239, en=final_door_requirement_message, jp=final_door_requirement_message)

        # Not needed until we figure out how to display the correct amount
        # self.messages.add_message(19482, en="Important Checks Found")

        if crit_mode:
            self.messages.add_message(
                17201,
                en="{:color #FF000080}Beginner (WARNING)",
                jp="{:color #FF000080}ビギナーモード (注意!)",
            )
            self.messages.add_message(
                17202,
                en="{:color #FF000080}Standard (WARNING)",
                jp="{:color #FF000080}スタンダードモード (注意!)",
            )
            self.messages.add_message(
                17203,
                en="{:color #FF000080}Proud (WARNING)",
                jp="{:color #FF000080}プラウドモード (注意!)",
            )
            self.messages.add_message(
                17204,
                en="An easier mode for beginners.\n{:color #FF000080}Critical Bonuses are turned on. The seven\nrandom starting items will be unobtainable.",
                jp="敵が弱く サクサクすすめるかんたんモードです\n{:color #FF000080}クリティカル特典が有効になっていますが、このモード\nではゲーム開始時に7つの特典を受け取れません",
            )
            self.messages.add_message(
                17205,
                en="A balanced mode best for those challenging\nthis game for the first time.\n{:color #FF000080}Critical Bonuses are turned on. The seven\nrandom starting items will be unobtainable.",
                jp="初プレイにピッタリの ほどよいバランスのモードです\n{:color #FF000080}クリティカル特典が有効になっていますが、このモード\nではゲーム開始時に7つの特典を受け取れません",
            )
            self.messages.add_message(
                17206,
                en="A difficult mode with stronger enemies.\nBest for those seeking a challenge.\n{:color #FF000080}Critical Bonuses are turned on. The seven\nrandom starting items will be unobtainable.",
                jp="敵が強く スリリングなバトルがたのしめるモードです\n{:color #FF000080}クリティカル特典が有効になっていますが、このモード\nではゲーム開始時に7つの特典を受け取れません",
            )
            self.messages.add_message(
                20020,
                en="A true test of skill for the adept. Begin\nwith certain abilities and other perks.\n{:color #F0F00080}Critical Bonuses are turned on. The seven\nstarting items have been randomized.",
                jp="アクションのウデがためされる 上級者向けのモードです\n{:color #FF000080}クリティカル特典が有効になっています。ゲーム開始時\nにランダムな7つの特典を受け取れます",
            )
        else:
            self.messages.add_message(
                20020,
                en="A true test of skill for the adept. Begin\nwith certain abilities and other perks.\n{:color #F0F00080}Critical Bonuses are turned off. The seven\nstarting items will be junk.",
                jp="アクションのウデがためされる 上級者向けのモードです\n{:color #FF000080}クリティカル特典が無効になっています。ゲーム開始時\nにランダムな7つのジャンクアイテムを受け取れます",
            )

    def write_as_data_split_assets(self):
        """Adds assets and files to the mod for AS/Data split."""
        for as_room in ["hb32", "hb33", "hb34", "hb38"]:
            script_name = f"{as_room}evt.script"
            source_name = _relative_mod_file(f"as_data_split/{script_name}")
            self._add_areadata_script_asset(ard_id=as_room, source_name=source_name)
            self.out_zip.write(
                resource_path(f"static/as_data_split/{script_name}"), source_name
            )

    def write_puzzle_assets(self, modified_puzzle_binary: bytearray):
        """Adds assets and files to the mod for modified puzzle rewards."""
        source_name = _relative_mod_file("modified_puzzle.bin")

        source_list = [
            {
                "name": "puzz",
                "type": "jimidata",
                "method": "copy",
                "source": [{"name": source_name}],
            }
        ]
        self.mod_yml.add_asset(
            {"name": "menu/fm/jiminy.bar", "method": "binarc", "source": source_list}
        )
        self.mod_yml.add_asset(
            {
                "name": "menu/us/jiminy.bar",
                "platform": "pc",
                "multi": [
                    {"name": "menu/fr/jiminy.bar"},
                    {"name": "menu/gr/jiminy.bar"},
                    {"name": "menu/it/jiminy.bar"},
                    {"name": "menu/sp/jiminy.bar"},
                    {"name": "menu/uk/jiminy.bar"},
                ],
                "method": "binarc",
                "source": source_list,
            }
        )

        self.out_zip.writestr(source_name, modified_puzzle_binary)

    def write_shop_assets(self, modified_shop_binary: bytearray):
        """Adds assets and files to the mod for modified shop content."""
        source_name = _relative_mod_file("modified_shop.bin")

        self.mod_yml.add_asset_source(
            "03system.bin",
            {
                "name": "shop",
                "type": "unknown41",
                "method": "copy",
                "source": [{"name": source_name}],
            },
        )

        self.out_zip.writestr(source_name, modified_shop_binary)

    def write_synth_assets(
        self,
        modified_recipes_binary: bytearray,
        modified_requirements_binary: bytearray,
    ):
        """Adds assets and files to the mod for modified synthesis recipes and requirements."""
        recipes_source_name = _relative_mod_file("modified_synth.bin")
        requirements_source_name = _relative_mod_file("modified_synth_reqs.bin")

        source_list = [
            {
                "name": "reci",
                "type": "synthesis",
                "method": "copy",
                "source": [{"name": recipes_source_name}],
            },
            {
                "name": "cond",
                "type": "synthesis",
                "method": "copy",
                "source": [{"name": requirements_source_name}],
            },
        ]
        self.mod_yml.add_asset(
            {"name": "menu/fm/mixdata.bar", "method": "binarc", "source": source_list}
        )
        self.mod_yml.add_asset(
            {
                "name": "menu/us/mixdata.bar",
                "platform": "pc",
                "multi": [
                    {"name": "menu/fr/mixdata.bar"},
                    {"name": "menu/gr/mixdata.bar"},
                    {"name": "menu/it/mixdata.bar"},
                    {"name": "menu/sp/mixdata.bar"},
                    {"name": "menu/uk/mixdata.bar"},
                    {"name": "menu/jp/mixdata.bar"},
                ],
                "method": "binarc",
                "source": source_list,
            }
        )

        self.out_zip.writestr(recipes_source_name, modified_recipes_binary)
        self.out_zip.writestr(requirements_source_name, modified_requirements_binary)

    def write_skip_carpet_escape_assets(self):
        """Adds assets and files to the mod for magic carpet escape."""
        source_name = _relative_mod_file("skip_carpet_escape.script")
        self._add_areadata_script_asset(ard_id="al11", source_name=source_name)
        self.out_zip.write(
            resource_path("static/skip_carpet_escape.script"), source_name
        )

    def write_block_cor_skip_assets(self):
        """Adds assets and files to the mod for blocking CoR skip."""
        source_name = _relative_mod_file("disable_cor_skip.script")
        self._add_areadata_script_asset(ard_id="hb24", source_name=source_name)
        self.out_zip.write(resource_path("static/disable_cor_skip.script"), source_name)

    def write_block_shan_yu_skip_assets(self):
        """Adds assets and files to the mod for blocking Shan-Yu skip."""
        source_name = _relative_mod_file("disable_shan_yu_skip.script")
        self._add_areadata_script_asset(ard_id="mu09", source_name=source_name)
        self.out_zip.write(
            resource_path("static/disable_shan_yu_skip.script"), source_name
        )

    def write_atlantica_tutorial_skip_assets(self):
        """Adds assets and files to the mod for Atlantica tutorial skip."""
        source_name = _relative_mod_file("atlantica_skip.script")
        self._add_areadata_script_asset(ard_id="lm02", source_name=source_name)
        self.out_zip.write(resource_path("static/atlantica_skip.script"), source_name)

    def write_wardrobe_skip_assets(self):
        """Adds assets and files to the mod for wardrobe skip."""
        source_name = _relative_mod_file("wardrobe_skip.mset")
        self.mod_yml.add_asset(
            {
                "name": "obj/N_BB080_BTL.mset",
                "method": "copy",
                "source": [{"name": source_name}],
            }
        )
        self.out_zip.write(
            resource_path("static/wardrobe/N_BB080_BTL.mset"), source_name
        )

    def write_map_skip_assets(self):
        """Adds assets and files to the mod for Port Royal map skip."""
        ca_yml_source_name = _relative_mod_file("map_skip/ca.yml")
        libretto_bin_source_name = _relative_mod_file("map_skip/libretto-ca.bar")

        self.mod_yml.add_assets(
            [
                {
                    "name": "msg/us/ca.bar",
                    "multi": [
                        {"name": "msg/fr/ca.bar"},
                        {"name": "msg/gr/ca.bar"},
                        {"name": "msg/it/ca.bar"},
                        {"name": "msg/sp/ca.bar"},
                    ],
                    "method": "binarc",
                    "source": [
                        {
                            "name": "ca",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [{"name": ca_yml_source_name, "language": "en"}],
                        }
                    ],
                },
                {
                    "name": "msg/jp/ca.bar",
                    "platform": "ps2",
                    "method": "binarc",
                    "source": [
                        {
                            "name": "ca",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [{"name": ca_yml_source_name, "language": "en"}],
                        }
                    ],
                },
                {
                    "name": "msg/jp/ca.bar",
                    "platform": "pc",
                    "method": "binarc",
                    "source": [
                        {
                            "name": "ca",
                            "type": "list",
                            "method": "kh2msg",
                            "source": [
                                {
                                    "name": ca_yml_source_name,
                                    "language": "jp",  # Change this to je whenever we update the mods manager
                                }
                            ],
                        }
                    ],
                },
                {
                    "name": "libretto-ca.bar",
                    "method": "copy",
                    "source": [{"name": libretto_bin_source_name}],
                },
            ]
        )

        self.out_zip.write(resource_path("static/map_skip/ca.yml"), ca_yml_source_name)
        self.out_zip.write(
            resource_path("static/map_skip/libretto-ca.bar"), libretto_bin_source_name
        )

    def write_cmd_list_modifications(self, modified_cmd_list_yml: str):
        """Adds assets and files to the mod for command list modification."""
        source_name = _relative_mod_file(modified_cmd_list_yml)

        self.mod_yml.add_asset_source(
            "03system.bin",
            {
                "name": "cmd",
                "type": "list",
                "method": "listpatch",
                "source": [{"name": source_name, "type": "cmd"}],
            },
        )

        self.out_zip.write(
            resource_path(f"static/{modified_cmd_list_yml}"), source_name
        )

    def write_battle_level_assets(self, modified_battle_level_binary: bytearray):
        """Adds assets and files to the mod for modified battle levels."""
        source_name = _relative_mod_file("modified_btlv.bin")

        self.mod_yml.add_asset_source(
            "00battle.bin",
            {
                "method": "copy",
                "name": "btlv",
                "type": "list",
                "source": [{"name": source_name}],
            },
        )

        self.out_zip.writestr(source_name, modified_battle_level_binary)

    def write_keyblade_locking_lua(self):
        keyblade_lua_name = _relative_mod_file("keyblade_locking/F266B00B Keyblade Locking.lua")
        assets = [
            {
                "name": "scripts/F266B00B Keyblade Locking.lua",
                "method": "copy",
                "source": [{"name": keyblade_lua_name}],
            },
        ]
        self.mod_yml.add_assets(assets)

        self.out_zip.write(
            resource_path("static/keyblade_locking/keyblade.lua"), keyblade_lua_name
        )

    def write_goa_lua(self):
        goa_lua_name = _relative_mod_file("beta_files/F266B00B GoA ROM.lua")
        assets = [
            {
                "name": "scripts/F266B00B GoA ROM.lua",
                "method": "copy",
                "source": [{"name": goa_lua_name}],
            },
        ]
        self.mod_yml.add_assets(assets)

        self.out_zip.write(
            resource_path("static/F266B00B GoA ROM.lua"), goa_lua_name
        )


    def write_better_stt_assets(self, boss_enabled: bool):
        """Adds assets and files to the mod for 'Better STT'."""
        trinity_bar_source_name = _relative_mod_file("better_stt/trinity_zz.bar")
        b_ex100_mset_source_name = _relative_mod_file("better_stt/B_EX100.mset")
        f_tt010_mset_source_name = _relative_mod_file("better_stt/F_TT010.mset")
        p_ex110_mset_source_name = _relative_mod_file("better_stt/P_EX110.mset")
        # objlist_yml_source_name = _relative_mod_file("better_stt/ObjList_Better_STT.yml")
        w_ex010_rx_mset_source_name = _relative_mod_file("better_stt/W_EX010_RX.mset")
        b_ex100_sr_mset_source_name = _relative_mod_file("better_stt/B_EX100_SR.mset")

        assets = [
            {
                "name": "limit/fm/trinity_zz.bar",
                "multi": [
                    {"name": "limit/us/trinity_zz.bar"},
                    {"name": "limit/fr/trinity_zz.bar"},
                    {"name": "limit/gr/trinity_zz.bar"},
                    {"name": "limit/it/trinity_zz.bar"},
                    {"name": "limit/sp/trinity_zz.bar"},
                ],
                "method": "copy",
                "source": [{"name": trinity_bar_source_name}],
            },
            {
                "name": "obj/B_EX100.mset",
                "method": "copy",
                "source": [{"name": b_ex100_mset_source_name}],
            },
            {
                "name": "obj/F_TT010.mset",
                "method": "copy",
                "source": [{"name": f_tt010_mset_source_name}],
            },
            {
                "name": "obj/P_EX110.mset",
                "method": "copy",
                "source": [{"name": p_ex110_mset_source_name}],
            },
            # {
            #     "name": "00objentry.bin",
            #     "method": "listpatch",
            #     "type": "List",
            #     "source": [
            #         {
            #             "name": objlist_yml_source_name,
            #             "type": "objentry"
            #         }
            #     ]
            # },
            {
                "name": "obj/W_EX010_RX.mset",
                "method": "copy",
                "source": [{"name": w_ex010_rx_mset_source_name}],
            },
        ]
        if boss_enabled:
            assets.append(
                {
                    "name": "obj/B_EX100_SR.mset",
                    "method": "copy",
                    "source": [{"name": b_ex100_sr_mset_source_name}],
                }
            )
        self.mod_yml.add_assets(assets)

        self.out_zip.write(
            resource_path("static/better_stt/trinity_zz.bar"), trinity_bar_source_name
        )
        self.out_zip.write(
            resource_path("static/better_stt/B_EX100.mset"), b_ex100_mset_source_name
        )
        self.out_zip.write(
            resource_path("static/better_stt/F_TT010.mset"), f_tt010_mset_source_name
        )
        self.out_zip.write(
            resource_path("static/better_stt/P_EX110.mset"), p_ex110_mset_source_name
        )
        self.out_zip.write(
            resource_path("static/better_stt/W_EX010_RX.mset"),
            w_ex010_rx_mset_source_name,
        )
        # out_zip.write(resource_path("static/better_stt/ObjList_Better_STT.yml"), objlist_yml_source_name)
        if boss_enabled:
            self.out_zip.write(
                resource_path("static/better_stt/B_EX100_SR.mset"),
                b_ex100_sr_mset_source_name,
            )

    def write_chest_visuals_assets(
        self, chest_assignments: list[ChestVisualAssignment]
    ):
        """Adds assets and files to the mod for 'Chest Visuals Match Contents'."""

        chest_assignments_by_file: dict[str, list[ChestVisualAssignment]] = {}
        for chest_assignment in chest_assignments:
            spawn_file_path = chest_assignment.spawn_file_path
            if spawn_file_path not in chest_assignments_by_file:
                chest_assignments_by_file[spawn_file_path] = []
            chest_assignments_by_file[spawn_file_path].append(chest_assignment)

        # Ards
        for spawn_file_path, visual_assignments in chest_assignments_by_file.items():
            with open(
                resource_path(f"static/chests/ard/{spawn_file_path}.spawn")
            ) as spawn_file:
                spawn_data = yaml.safe_load(spawn_file)

            for visual_assignment in visual_assignments:
                chest_index = visual_assignment.chest_index
                spawn_data[0]["Entities"][chest_index][
                    "ObjectId"
                ] = visual_assignment.chest_visual_id

            write_yaml_to_zip_file(
                self.out_zip,
                _relative_mod_file(f"chest/ard/{spawn_file_path}.yml"),
                spawn_data,
                sort_keys=False,
            )

        self.mod_yml.add_assets(self._get_chest_visual_assets())

        # Remastered Textures
        trash_source_name = _relative_mod_file("chest/remastered/trash.dds")
        abilities_source_name = _relative_mod_file("chest/remastered/abilities.dds")
        equipment_source_name = _relative_mod_file("chest/remastered/equipment.dds")
        forms_source_name = _relative_mod_file("chest/remastered/forms.dds")
        magic_source_name = _relative_mod_file("chest/remastered/magic.dds")
        pages_source_name = _relative_mod_file("chest/remastered/pages.dds")
        reports_source_name = _relative_mod_file("chest/remastered/reports.dds")
        stats_source_name = _relative_mod_file("chest/remastered/stats.dds")
        summons_source_name = _relative_mod_file("chest/remastered/summons.dds")
        unlocks_source_name = _relative_mod_file("chest/remastered/unlocks.dds")
        weapons_source_name = _relative_mod_file("chest/remastered/weapons.dds")
        proofs_glow_source_name = _relative_mod_file("chest/remastered/proofs_glow.dds")
        proofs_source_name = _relative_mod_file("chest/remastered/proofs.dds")
        self.out_zip.write(
            resource_path("static/chests/remastered/trash.dds"), trash_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/abilities.dds"),
            abilities_source_name,
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/forms.dds"), forms_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/magic.dds"), magic_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/pages.dds"), pages_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/reports.dds"), reports_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/stats.dds"), stats_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/summons.dds"), summons_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/unlocks.dds"), unlocks_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/weapons.dds"), weapons_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/proofs_glow.dds"),
            proofs_glow_source_name,
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/proofs.dds"), proofs_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/remastered/equipment.dds"), equipment_source_name
        )

        # Listpatch
        chest_obj_list_source_name = _relative_mod_file("chest/ChestObjList.yml")
        self.out_zip.write(
            resource_path("static/chests/ChestObjList.script"),
            chest_obj_list_source_name,
        )

        # MSET
        f_ex030_lk_source_name = _relative_mod_file("chest/obj/F_EX030_LK.mset")
        f_ex040_lk_source_name = _relative_mod_file("chest/obj/F_EX040_LK.mset")
        f_ex040_rx_source_name = _relative_mod_file("chest/obj/F_EX040_RX.mset")
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_LK.mset"), f_ex030_lk_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX040_LK.mset"), f_ex040_lk_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX040_RX.mset"), f_ex040_rx_source_name
        )

        # MDLX
        jnk_source_name = _relative_mod_file("chest/obj/F_EX030_JK2.mdlx")
        abl_source_name = _relative_mod_file("chest/obj/F_EX030_ABL.mdlx")
        eqp_source_name = _relative_mod_file("chest/obj/F_EX030_EQP.mdlx")
        frm_source_name = _relative_mod_file("chest/obj/F_EX030_FRM.mdlx")
        mag_source_name = _relative_mod_file("chest/obj/F_EX030_MAG.mdlx")
        pag_source_name = _relative_mod_file("chest/obj/F_EX030_PAG.mdlx")
        rep_source_name = _relative_mod_file("chest/obj/F_EX030_REP.mdlx")
        sta_source_name = _relative_mod_file("chest/obj/F_EX030_STA.mdlx")
        smn_source_name = _relative_mod_file("chest/obj/F_EX030_SMN.mdlx")
        vst_source_name = _relative_mod_file("chest/obj/F_EX030_VST.mdlx")
        wep_source_name = _relative_mod_file("chest/obj/F_EX030_WEP.mdlx")
        prf_source_name = _relative_mod_file("chest/obj/F_EX040_PRF.mdlx")
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_JK2.mdlx"), jnk_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_ABL.mdlx"), abl_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_EQP.mdlx"), eqp_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_FRM.mdlx"), frm_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_MAG.mdlx"), mag_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_PAG.mdlx"), pag_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_REP.mdlx"), rep_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_STA.mdlx"), sta_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_SMN.mdlx"), smn_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_VST.mdlx"), vst_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX030_WEP.mdlx"), wep_source_name
        )
        self.out_zip.write(
            resource_path("static/chests/obj/F_EX040_PRF.mdlx"), prf_source_name
        )

    def validate_and_write_mod_yml(
        self, zip_data: io.BytesIO, settings: RandomizerSettings
    ) -> io.BytesIO:
        """Performs some validation and deduplication of assets in the mod, and writes the mod.yml to the zip file."""

        mod_data = self.mod_yml.data

        # merge cmd mods if there are two
        #  first, find all the list patches we are assigning
        listpatch_file_paths: list[str] = []
        listpatch_contents: list[dict[str, Any]] = []

        for modded_file in self.mod_yml.find_assets("03system.bin"):
            for source_index, source in enumerate(modded_file["source"]):
                if source["name"] == "cmd":
                    listpatch_file_paths.append(
                        source["source"][0]["name"].replace("\\", "/")
                    )

        merged_command_list_source = _relative_mod_file("cmd_list_merged.yml")

        # if there are multiple cmd listpatches, we have to read all the contents to create new file
        if len(listpatch_file_paths) > 1:
            with ZipFile(zip_data, "r") as current_zip:
                for listpatch_file_path in listpatch_file_paths:
                    path = zipfile.Path(current_zip, at=listpatch_file_path)
                    with path.open(encoding="UTF-8") as current_listpatch_file:
                        listpatch_contents.extend(
                            yaml.safe_load(current_listpatch_file)
                        )

            # remove all instances of 03_system cmd listpatches (we'll add another afterward)
            num_03system_entries = 0
            delete_asset_indices: list[int] = []
            for asset_index, asset in enumerate(mod_data["assets"]):
                delete_source_indices: list[int] = []
                if asset["name"] == "03system.bin":
                    num_03system_entries += 1
                    for source_index, source in enumerate(asset["source"]):
                        if source["name"] == "cmd":
                            delete_source_indices.append(source_index)
                    asset["source"] = [
                        src
                        for src_index, src in enumerate(asset["source"])
                        if src_index not in delete_source_indices
                    ]
                    if len(asset["source"]) == 0:
                        delete_asset_indices.append(asset_index)
            # assumes that empty 03system lists are only happening from
            # listpatches of cmd, and we don't want to remove the only
            # instance of this in the yaml, so only remove empty entries
            # if they are duplicates
            if num_03system_entries > 1:
                mod_data["assets"] = [
                    asset
                    for a_index, asset in enumerate(mod_data["assets"])
                    if a_index not in delete_asset_indices
                ]

            self.mod_yml.add_asset_source(
                "03system.bin",
                {
                    "name": "cmd",
                    "method": "listpatch",
                    "type": "list",
                    "source": [{"name": merged_command_list_source, "type": "cmd"}],
                },
            )

        for modded_file in ["00battle.bin"]:
            first_asset_index: Optional[int] = None
            delete_asset_indices: list[int] = []
            for asset_index, asset in enumerate(mod_data["assets"]):
                if asset["name"] == modded_file:
                    if first_asset_index:
                        delete_asset_indices.append(asset_index)
                        # add these sources into this group
                        mod_data["assets"][first_asset_index]["source"] += asset[
                            "source"
                        ]
                    else:
                        first_asset_index = asset_index
            # delete all duplicates
            mod_data["assets"] = [
                asset
                for a_index, asset in enumerate(mod_data["assets"])
                if a_index not in delete_asset_indices
            ]

        # add multi support for TWTNW NPCs, msg/jp/eh.bar (TODO: remove this when khbr does it on its own)
        for asset in mod_data["assets"]:
            if asset["name"] == "msg/jp/eh.bar":
                asset["multi"].append({"name": "msg/fr/eh.bar"})
                asset["multi"].append({"name": "msg/gr/eh.bar"})
                asset["multi"].append({"name": "msg/it/eh.bar"})
                asset["multi"].append({"name": "msg/sp/eh.bar"})

        # now that the mod yml is proper, we want to add any merged files into the zip, along with the mod.yml
        with ZipFile(zip_data, "a", ZIP_DEFLATED) as current_zip:
            self.mod_yml.write_to_zip_file(current_zip)
            if len(listpatch_contents) > 0:
                write_yaml_to_zip_file(
                    current_zip,
                    merged_command_list_source,
                    listpatch_contents,
                    sort_keys=False,
                )

        zip_data.seek(0)
        return zip_data

    def write_mod_ymls(self, include_main_mod_yml: bool):
        """Writes the output for the various mod YAML files."""
        zip_file = self.out_zip

        self.bonuses.write_to_zip_file(zip_file)
        self.form_levels.write_to_zip_file(zip_file)
        self.items.write_to_zip_file(zip_file)
        self.level_ups.write_to_zip_file(zip_file)
        self.messages.write_to_zip_file(zip_file)
        self.journal_txt.write_to_zip_file(zip_file)
        self.player_params.write_to_zip_file(zip_file)
        if self.prize_table.has_entries():
            # Prize table only has entries if certain settings are on, so we may need to add its asset as well
            self._add_prize_table_asset()
            self.prize_table.write_to_zip_file(zip_file)
        if self.atkp_organizer.has_entries():
            self._add_atkp_asset()
            self.atkp_organizer.write_to_zip_file(zip_file)
        self.treasures.write_to_zip_file(zip_file)

        # Main mod.yml comes last in case any additional assets were added
        if include_main_mod_yml:
            self.mod_yml.write_to_zip_file(zip_file)

    def _add_areadata_script_asset(self, ard_id: str, source_name: str):
        self.mod_yml.add_asset(
            {
                "name": f"ard/{ard_id}.ard",
                "multi": [
                    {"name": f"ard/fr/{ard_id}.ard"},
                    {"name": f"ard/gr/{ard_id}.ard"},
                    {"name": f"ard/it/{ard_id}.ard"},
                    {"name": f"ard/jp/{ard_id}.ard"},
                    {"name": f"ard/sp/{ard_id}.ard"},
                    {"name": f"ard/us/{ard_id}.ard"},
                ],
                "method": "binarc",
                "source": [
                    {
                        "name": "evt",
                        "type": "areadatascript",
                        "method": "areadatascript",
                        "source": [{"name": source_name}],
                    }
                ],
            }
        )

    def _add_prize_table_asset(self):
        self.mod_yml.add_asset_source(
            "00battle.bin",
            {
                "name": "przt",
                "type": "list",
                "method": "listpatch",
                "source": [{"name": self.prize_table.source_name, "type": "przt"}],
            },
        )

    @staticmethod
    def _get_chest_visual_assets() -> list[Asset]:
        assets: list[Asset] = []

        basic_types = {
            "JK2": "trash",
            "ABL": "abilities",
            "EQP": "equipment",
            "FRM": "forms",
            "MAG": "magic",
            "PAG": "pages",
            "REP": "reports",
            "STA": "stats",
            "SMN": "summons",
            "VST": "unlocks",
            "WEP": "weapons",
        }

        # ARD edits
        for room_1 in [
            "al00",
            "al01",
            "al06",
            "al07",
            "al10",
            "al11",
            "al12",
            "al13",
            "bb02",
            "bb03",
            "bb06",
            "bb07",
            "bb08",
            "bb09",
            "bb10",
            "ca00",
            "ca02",
            "ca09",
            "ca11",
            "ca12",
            "ca13",
            "ca14",
            "ca15",
            "dc01",
            "dc03",
            "eh02",
            "eh03",
            "eh04",
            "eh06",
            "eh09",
            "eh12",
            "eh17",
            "hb03",
            "hb09",
            "hb11",
            "hb12",
            "hb18",
            "hb21",
            "hb22",
            "hb23",
            "hb24",
            "he03",
            "he10",
            "he11",
            "he12",
            "he15",
            "he16",
            "he17",
            "lk00",
            "lk03",
            "lk05",
            "lk06",
            "lk07",
            "lk08",
            "lk09",
            "mu00",
            "mu02",
            "mu03",
            "mu05",
            "mu06",
            "mu11",
            "nm00",
            "nm01",
            "nm02",
            "nm04",
            "nm06",
            "nm08",
            "po01",
            "po02",
            "po03",
            "po04",
            "po05",
            "po09",
            "tr00",
            "tr01",
            "tr04",
            "tr05",
            "tr08",
            "wi00",
            "wi01",
            "wi02",
            "tt32",
            "tt33",
        ]:
            assets.append(
                {
                    "name": "ard/" + room_1 + ".ard",
                    "multi": [
                        {"name": "ard/jp/" + room_1 + ".ard"},
                        {"name": "ard/us/" + room_1 + ".ard"},
                        {"name": "ard/fr/" + room_1 + ".ard"},
                        {"name": "ard/gr/" + room_1 + ".ard"},
                        {"name": "ard/it/" + room_1 + ".ard"},
                        {"name": "ard/sp/" + room_1 + ".ard"},
                    ],
                    "method": "binarc",
                    "source": [
                        {
                            "name": "m_70",
                            "type": "areadataspawn",
                            "method": "spawnpoint",
                            "source": [
                                {
                                    "name": _relative_mod_file(
                                        "chest/ard/" + room_1 + "/m_70.yml"
                                    )
                                }
                            ],
                        }
                    ],
                }
            )

        for room_2 in [
            "bb12",
            "hb05",
            "hb06",
            "hb26",
            "tt09",
            "tt10",
            "tt15",
            "tt16",
            "tt17",
            "tt22",
        ]:
            assets.append(
                {
                    "name": "ard/" + room_2 + ".ard",
                    "multi": [
                        {"name": "ard/jp/" + room_2 + ".ard"},
                        {"name": "ard/us/" + room_2 + ".ard"},
                        {"name": "ard/fr/" + room_2 + ".ard"},
                        {"name": "ard/gr/" + room_2 + ".ard"},
                        {"name": "ard/it/" + room_2 + ".ard"},
                        {"name": "ard/sp/" + room_2 + ".ard"},
                    ],
                    "method": "binarc",
                    "source": [
                        {
                            "name": "m_70",
                            "type": "areadataspawn",
                            "method": "spawnpoint",
                            "source": [
                                {
                                    "name": _relative_mod_file(
                                        "chest/ard/" + room_2 + "/m_70.yml"
                                    )
                                }
                            ],
                        },
                        {
                            "name": "m_71",
                            "type": "areadataspawn",
                            "method": "spawnpoint",
                            "source": [
                                {
                                    "name": _relative_mod_file(
                                        "chest/ard/" + room_2 + "/m_71.yml"
                                    )
                                }
                            ],
                        },
                    ],
                }
            )

        for room_3 in [
            "tt07",
            "tt13",
            "tt14",
            "tt25",
            "tt26",
            "tt27",
            "tt28",
            "tt36",
            "tt37",
        ]:
            assets.append(
                {
                    "name": "ard/" + room_3 + ".ard",
                    "multi": [
                        {"name": "ard/jp/" + room_3 + ".ard"},
                        {"name": "ard/us/" + room_3 + ".ard"},
                        {"name": "ard/fr/" + room_3 + ".ard"},
                        {"name": "ard/gr/" + room_3 + ".ard"},
                        {"name": "ard/it/" + room_3 + ".ard"},
                        {"name": "ard/sp/" + room_3 + ".ard"},
                    ],
                    "method": "binarc",
                    "source": [
                        {
                            "name": "m_71",
                            "type": "areadataspawn",
                            "method": "spawnpoint",
                            "source": [
                                {
                                    "name": _relative_mod_file(
                                        "chest/ard/" + room_3 + "/m_71.yml"
                                    )
                                }
                            ],
                        }
                    ],
                }
            )

        # MDLXs
        for basic_type_suffix, _ in basic_types.items():
            assets.append(
                {
                    "name": f"obj/F_EX030_{basic_type_suffix}.mdlx",
                    "multi": [
                        {"name": f"obj/F_EX050_{basic_type_suffix}.mdlx"},
                    ],
                    "method": "copy",
                    "source": [
                        {
                            "name": _relative_mod_file(
                                f"chest/obj/F_EX030_{basic_type_suffix}.mdlx"
                            )
                        }
                    ],
                }
            )
        # Proofs
        assets.append(
            {
                "name": "obj/F_EX040_PRF.mdlx",
                "multi": [
                    {"name": "obj/F_EX060_PRF.mdlx"},
                ],
                "method": "copy",
                "source": [{"name": _relative_mod_file("chest/obj/F_EX040_PRF.mdlx")}],
            }
        )

        # chest *.a.us (PC)
        assets.append(
            {
                "name": "obj/F_EX030_JK2.a.us",
                "platform": "pc",
                "multi": [
                    {"name": "obj/F_EX030_ABL.a.us"},
                    {"name": "obj/F_EX030_EQP.a.us"},
                    {"name": "obj/F_EX030_FRM.a.us"},
                    {"name": "obj/F_EX030_MAG.a.us"},
                    {"name": "obj/F_EX030_PAG.a.us"},
                    {"name": "obj/F_EX030_REP.a.us"},
                    {"name": "obj/F_EX030_STA.a.us"},
                    {"name": "obj/F_EX030_SMN.a.us"},
                    {"name": "obj/F_EX030_VST.a.us"},
                    {"name": "obj/F_EX030_WEP.a.us"},
                    {"name": "obj/F_EX050_JK2.a.us"},
                    {"name": "obj/F_EX050_ABL.a.us"},
                    {"name": "obj/F_EX050_EQP.a.us"},
                    {"name": "obj/F_EX050_FRM.a.us"},
                    {"name": "obj/F_EX050_MAG.a.us"},
                    {"name": "obj/F_EX050_PAG.a.us"},
                    {"name": "obj/F_EX050_REP.a.us"},
                    {"name": "obj/F_EX050_STA.a.us"},
                    {"name": "obj/F_EX050_SMN.a.us"},
                    {"name": "obj/F_EX050_VST.a.us"},
                    {"name": "obj/F_EX050_WEP.a.us"},
                ],
                "method": "copy",
                "source": [
                    {
                        "name": "obj/F_EX030.a.us",
                        "type": "internal",
                    }
                ],
            }
        )
        assets.append(
            {
                "name": "obj/F_EX040_PRF.a.us",
                "platform": "pc",
                "multi": [
                    {"name": "obj/F_EX060_PRF.a.us"},
                ],
                "method": "copy",
                "source": [
                    {
                        "name": "obj/F_EX040_EH.a.us",
                        "type": "internal",
                    }
                ],
            }
        )

        # chest *.a.fm (PS2)
        assets.append(
            {
                "name": "obj/F_EX030_JK2.a.fm",
                "platform": "ps2",
                "multi": [
                    {"name": "obj/F_EX030_ABL.a.fm"},
                    {"name": "obj/F_EX030_EQP.a.fm"},
                    {"name": "obj/F_EX030_FRM.a.fm"},
                    {"name": "obj/F_EX030_MAG.a.fm"},
                    {"name": "obj/F_EX030_PAG.a.fm"},
                    {"name": "obj/F_EX030_REP.a.fm"},
                    {"name": "obj/F_EX030_STA.a.fm"},
                    {"name": "obj/F_EX030_SMN.a.fm"},
                    {"name": "obj/F_EX030_VST.a.fm"},
                    {"name": "obj/F_EX030_WEP.a.fm"},
                    {"name": "obj/F_EX050_JK2.a.fm"},
                    {"name": "obj/F_EX050_ABL.a.fm"},
                    {"name": "obj/F_EX050_EQP.a.fm"},
                    {"name": "obj/F_EX050_FRM.a.fm"},
                    {"name": "obj/F_EX050_MAG.a.fm"},
                    {"name": "obj/F_EX050_PAG.a.fm"},
                    {"name": "obj/F_EX050_REP.a.fm"},
                    {"name": "obj/F_EX050_STA.a.fm"},
                    {"name": "obj/F_EX050_SMN.a.fm"},
                    {"name": "obj/F_EX050_VST.a.fm"},
                    {"name": "obj/F_EX050_WEP.a.fm"},
                ],
                "method": "copy",
                "source": [
                    {
                        "name": "obj/F_EX030.a.fm",
                        "type": "internal",
                    }
                ],
            }
        )
        assets.append(
            {
                "name": "obj/F_EX040_PRF.a.fm",
                "platform": "ps2",
                "multi": [
                    {"name": "obj/F_EX060_PRF.a.fm"},
                ],
                "method": "copy",
                "source": [
                    {
                        "name": "obj/F_EX040_EH.a.fm",
                        "type": "internal",
                    }
                ],
            }
        )

        # MSETs
        # PL Small Chest
        assets.append(
            {
                "name": "obj/F_EX030_LK.mset",
                "method": "copy",
                "source": [{"name": _relative_mod_file("chest/obj/F_EX030_LK.mset")}],
            }
        )
        # PL Big Chest
        assets.append(
            {
                "name": "obj/F_EX040_LK.mset",
                "method": "copy",
                "source": [{"name": _relative_mod_file("chest/obj/F_EX040_LK.mset")}],
            }
        )
        # STT Big Chest (Credit to Zurph)
        assets.append(
            {
                "name": "obj/F_EX040_RX.mset",
                "method": "copy",
                "source": [{"name": _relative_mod_file("chest/obj/F_EX040_RX.mset")}],
            }
        )

        # Remastered textures (Credit to Kayya and Televo)
        for basic_type_suffix, basic_type_name in basic_types.items():
            assets.append(
                {
                    "name": f"remastered/obj/F_EX030_{basic_type_suffix}.mdlx/-0.dds",
                    "multi": [
                        {
                            "name": f"remastered/obj/F_EX050_{basic_type_suffix}.mdlx/-0.dds"
                        },
                    ],
                    "method": "copy",
                    "source": [
                        {
                            "name": _relative_mod_file(
                                f"chest/remastered/{basic_type_name}.dds"
                            )
                        }
                    ],
                }
            )
        # Proofs
        assets.append(
            {
                "name": "remastered/obj/F_EX040_PRF.mdlx/-0.dds",
                "multi": [
                    {"name": "remastered/obj/F_EX060_PRF.mdlx/-0.dds"},
                ],
                "method": "copy",
                "source": [
                    {"name": _relative_mod_file("chest/remastered/proofs_glow.dds")}
                ],
            }
        )
        assets.append(
            {
                "name": "remastered/obj/F_EX040_PRF.mdlx/-1.dds",
                "multi": [
                    {"name": "remastered/obj/F_EX060_PRF.mdlx/-1.dds"},
                ],
                "method": "copy",
                "source": [{"name": _relative_mod_file("chest/remastered/proofs.dds")}],
            }
        )

        # 00objentry edits
        assets.append(
            {
                "name": "00objentry.bin",
                "method": "listpatch",
                "type": "List",
                "source": [
                    {
                        "name": _relative_mod_file("chest/ChestObjList.yml"),
                        "type": "objentry",
                    }
                ],
            }
        )

        return assets

    def _add_atkp_asset(self):
        self.mod_yml.add_asset_source(
            "00battle.bin",
            {
                "name": "atkp",
                "type": "list",
                "method": "listpatch",
                "source": [{"name": self.atkp_organizer.source_name, "type": "atkp"}],
            },
        )

    def _get_atkp_organizer(self):
        return self.atkp_organizer


class CosmeticsModAppender:

    def __init__(self, out_zip: ZipFile, mod_yml: ModYml):
        self.out_zip = out_zip
        self.mod_yml = mod_yml

    def write_music_rando_assets(self, music_assets: list[Asset], music_replacements: dict[str, str]):
        self.mod_yml.add_assets(music_assets)
        self._write_music_replacements(music_replacements)

    def write_keyblade_rando_assets(self, keyblade_assets: list[Asset], keyblade_replacements: dict[str, str]):
        self.mod_yml.add_assets(keyblade_assets)
        self._write_keyblade_replacements(keyblade_replacements)

    def write_rando_themed_texture_assets(self):
        """Adds assets and files to the mod for 'Add Randomizer-Themed Textures'."""
        goa_computer_source_name = _relative_mod_file("textures/goa/randomizer-themed-computer.dds")
        tt_computer_source_name = _relative_mod_file("textures/tt-computer/randomizer-themed-computer-tt.dds")
        data_door_source_name = _relative_mod_file("textures/data-door/data-door-logo.dds")
        final_arena_logo_source_name = _relative_mod_file("textures/final-arena/randomizer-logo.dds")

        self.mod_yml.add_asset(
            {
                "name": "remastered/map/hb26.map/-40.dds",
                "platform": "pc",
                "method": "copy",
                "source": [{"name": goa_computer_source_name}],
            },
        )
        self.mod_yml.add_asset(
            {
                "name": "remastered/map/tt21.map/-84.dds",
                "platform": "pc",
                "method": "copy",
                "source": [{"name": tt_computer_source_name}],
            },
        )
        self.mod_yml.add_asset(
            {
                "name": "remastered/obj/F_WI550.mdlx/-2.dds",
                "platform": "pc",
                "method": "copy",
                "source": [{"name": data_door_source_name}],
            },
        )
        self.mod_yml.add_asset(
            {
                "name": "remastered/map/eh20.map/-4.dds",
                "platform": "pc",
                "multi": [
                    {"name": "remastered/map/eh20.map/-6.dds"},
                    {"name": "remastered/map/eh20.map/-9.dds"},
                ],
                "method": "copy",
                "source": [{"name": final_arena_logo_source_name}],
            },
        )

        self.out_zip.write(
            resource_path("static/textures/goa/randomizer-themed-computer.dds"),
            goa_computer_source_name,
        )
        self.out_zip.write(
            resource_path("static/textures/tt-computer/randomizer-themed-computer-tt.dds"),
            tt_computer_source_name,
        )
        self.out_zip.write(
            resource_path("static/textures/data-door/data-door-logo.dds"),
            data_door_source_name,
        )
        self.out_zip.write(
            resource_path("static/textures/final-arena/randomizer-logo.dds"),
            final_arena_logo_source_name,
        )

    def _write_keyblade_replacements(self, replacements: dict[str, str]):
        if len(replacements) > 0:
            keyblade_replacements_string = ""
            for original, replacement in replacements.items():
                keyblade_replacements_string += f"[{original}] was replaced by [{replacement}]\n"
            self.out_zip.writestr("keyblade-replacement-list.txt", keyblade_replacements_string)

    def _write_music_replacements(self, replacements: dict[str, str]):
        if len(replacements) > 0:
            music_replacements_string = ""
            for original, replacement in replacements.items():
                music_replacements_string += f"[{original}] was replaced by [{replacement}]\n"
            self.out_zip.writestr("music-replacement-list.txt", music_replacements_string)
