#include "mod_dragon_legacy.h"
#include "Chat.h"
#include "CommandScript.h"
#include "Config.h"
#include "Creature.h"
#include "DatabaseEnv.h"
#include "Player.h"
#include "ScriptedGossip.h"
#include "ScriptMgr.h"
#include "SpellScript.h"

using namespace Acore::ChatCommands;

namespace
{
    constexpr uint32 GOSSIP_ACTION_LEARN_DRAGON_FORM = 1;
    constexpr uint32 DRAGON_LEGACY_NPC_TEXT = 901000;

    // Dragon Form (900000) and Whelp Breath (900001) are from-scratch spell IDs with no entry in
    // the client's own Spell.dbc -- confirmed in-game that learning them via Player::learnSpell()
    // persists to character_spell, but the client has nothing to render/cast for them (no name, no
    // icon, no macro resolution). Unlock is tracked here instead, and Dragon Form is applied via a
    // triggered CastSpell from the .dragon-rawr command rather than asking the player to cast a
    // "known" spell themselves -- see modules/mod-dragon-legacy/PLAN.md ("v1.1: spell -> command
    // pivot") for the full writeup and the same lesson learned independently in mod-waygate-network.
    bool IsDragonLegacyUnlocked(Player* player)
    {
        QueryResult result = CharacterDatabase.Query(
            "SELECT 1 FROM mod_dragon_legacy_unlocked WHERE guid = {}",
            player->GetGUID().GetCounter());

        return result != nullptr;
    }
}

class DragonLegacy_NPC : public CreatureScript
{
public:
    DragonLegacy_NPC() : CreatureScript("DragonLegacy_NPC") { }

    bool OnGossipHello(Player* player, Creature* creature) override
    {
        ClearGossipMenuFor(player);

        if (sConfigMgr->GetOption("DragonLegacy.Enable", true) && !IsDragonLegacyUnlocked(player))
        {
            AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Teach me the way of the dragon!",
                GOSSIP_SENDER_MAIN, GOSSIP_ACTION_LEARN_DRAGON_FORM);
        }

        SendGossipMenuFor(player, DRAGON_LEGACY_NPC_TEXT, creature->GetGUID());
        return true;
    }

    bool OnGossipSelect(Player* player, Creature* /*creature*/, uint32 sender, uint32 action) override
    {
        if (sender != GOSSIP_SENDER_MAIN)
            return false;

        CloseGossipMenuFor(player);

        if (action == GOSSIP_ACTION_LEARN_DRAGON_FORM && sConfigMgr->GetOption("DragonLegacy.Enable", true)
            && !IsDragonLegacyUnlocked(player))
        {
            CharacterDatabase.Execute(
                "INSERT INTO mod_dragon_legacy_unlocked (guid) VALUES ({})",
                player->GetGUID().GetCounter());

            ChatHandler(player->GetSession()).PSendSysMessage(
                "You have unlocked the way of the dragon. Use .dragon-rawr to transform, and "
                ".dragon-rawr breath to unleash Whelp Breath on your target.");
        }

        return true;
    }
};

class spell_dragon_legacy_whelp_breath : public SpellScript
{
    PrepareSpellScript(spell_dragon_legacy_whelp_breath);

    SpellCastResult CheckDragonForm()
    {
        if (!GetCaster()->HasAura(SPELL_DRAGON_FORM))
            return SPELL_FAILED_ONLY_SHAPESHIFT;

        return SPELL_CAST_OK;
    }

    void Register() override
    {
        OnCheckCast += SpellCheckCastFn(spell_dragon_legacy_whelp_breath::CheckDragonForm);
    }
};

class DragonLegacyCommandScript : public CommandScript
{
public:
    DragonLegacyCommandScript() : CommandScript("DragonLegacyCommandScript") { }

    ChatCommandTable GetCommands() const override
    {
        static ChatCommandTable dragonRawrCommandTable =
        {
            { "breath",       HandleDragonBreathCommand,       SEC_PLAYER,     Console::No },
            { "testvehicle",  HandleDragonTestVehicleCommand,  SEC_GAMEMASTER, Console::No },
            { "testvehicle2", HandleDragonTestVehicle2Command, SEC_GAMEMASTER, Console::No },
            { "testvehicle3", HandleDragonTestVehicle3Command, SEC_GAMEMASTER, Console::No },
            { "testvehicle4", HandleDragonTestVehicle4Command, SEC_GAMEMASTER, Console::No },
            { "",             HandleDragonFormCommand,         SEC_PLAYER,     Console::No },
        };

        static ChatCommandTable commandTable =
        {
            { "dragon-rawr", dragonRawrCommandTable },
        };

        return commandTable;
    }

    static bool HandleDragonFormCommand(ChatHandler* handler)
    {
        Player* player = handler->GetPlayer();
        if (!player)
            return true;

        if (!sConfigMgr->GetOption("DragonLegacy.Enable", true))
        {
            handler->PSendSysMessage("The way of the dragon is currently disabled.");
            return true;
        }

        if (player->HasAura(SPELL_DRAGON_FORM))
        {
            player->RemoveAurasDueToSpell(SPELL_DRAGON_FORM);
            return true;
        }

        if (!IsDragonLegacyUnlocked(player))
        {
            handler->PSendSysMessage("You have not learned the way of the dragon yet.");
            return true;
        }

        player->CastSpell(player, SPELL_DRAGON_FORM, true);
        return true;
    }

    static bool HandleDragonBreathCommand(ChatHandler* handler)
    {
        Player* player = handler->GetPlayer();
        if (!player)
            return true;

        if (!player->HasAura(SPELL_DRAGON_FORM))
        {
            handler->PSendSysMessage("You must take dragon form before you can use Whelp Breath.");
            return true;
        }

        Unit* target = player->GetSelectedUnit();
        if (!target)
        {
            handler->PSendSysMessage("You need a target to breathe fire at.");
            return true;
        }

        player->CastSpell(target, SPELL_WHELP_BREATH, true);
        return true;
    }

    // Shared exit path for all testvehicle* commands below -- returns true if the player was on a
    // test vehicle and has now been taken off it (and it despawned), false if they weren't on one.
    // Always restores native object scale on exit -- harmless no-op for testvehicle/testvehicle2
    // (which never touch it), undoes testvehicle3's shrink-to-hide trick.
    static bool ExitTestVehicleIfMounted(Player* player, ChatHandler* handler)
    {
        if (!player->GetVehicle())
            return false;

        Unit* base = player->GetVehicleBase();
        player->ExitVehicle();
        player->SetObjectScale(player->GetNativeObjectScale());

        if (base)
            if (Creature* baseCreature = base->ToCreature())
                baseCreature->DespawnOrUnsummon();

        handler->PSendSysMessage("Exited the test vehicle.");
        return true;
    }

    // v2 prototype only, see PLAN.md "v2: vehicle-based full transform" -- summons a vehicle
    // creature and seats the player in it via the same public API core scripts use
    // (Unit::EnterVehicle), to prove the possess + bar mechanic works before building the real
    // thing. No lockout logic yet. GM-only. Shared by testvehicle and testvehicle2.
    static bool ToggleTestVehicle(ChatHandler* handler, uint32 creatureEntry, char const* boardedMessage)
    {
        Player* player = handler->GetPlayer();
        if (!player)
            return true;

        if (ExitTestVehicleIfMounted(player, handler))
            return true;

        Creature* vehicle = player->SummonCreature(creatureEntry, player->GetPositionX(),
            player->GetPositionY(), player->GetPositionZ(), player->GetOrientation(),
            TEMPSUMMON_TIMED_DESPAWN, 600000);

        if (!vehicle)
        {
            handler->PSendSysMessage("Could not summon the test vehicle.");
            return true;
        }

        // Mirrors a workaround in the real Oculus drake AI (oculus.cpp, npc_oculus_drakeAI) for a
        // known "summoned in air with wrong animation state" issue -- trying it here in case it
        // also fixes the seethrough rendering seen on the drake test.
        vehicle->SetDisableGravity(false);
        vehicle->SetHover(false);

        player->EnterVehicle(vehicle, 0);
        handler->PSendSysMessage(boardedMessage);
        return true;
    }

    static bool HandleDragonTestVehicleCommand(ChatHandler* handler)
    {
        return ToggleTestVehicle(handler, NPC_TEST_RUBY_DRAKE,
            "Boarded the test drake -- check your action bar and whether it renders solid.");
    }

    // Control group: a real, well-tested, non-dragon/non-flying vehicle, to check whether the
    // seethrough issue is specific to the drake/flying-model or a general artifact of how this
    // test summons and boards vehicles.
    static bool HandleDragonTestVehicle2Command(ChatHandler* handler)
    {
        return ToggleTestVehicle(handler, NPC_TEST_DEMOLISHER,
            "Boarded the test demolisher (control group, non-dragon) -- check whether it renders solid.");
    }

    // Same drake model as testvehicle, but the vehicle container is swapped at runtime
    // (Unit::CreateVehicleKit) to VEHICLE_TEST_HIDE_PASSENGER, whose seat has
    // VEHICLE_SEAT_FLAG_HIDE_PASSENGER set -- tests whether the rider can be hidden on the drake's
    // model, i.e. "transform into" rather than "sit on top of."
    static bool HandleDragonTestVehicle3Command(ChatHandler* handler)
    {
        Player* player = handler->GetPlayer();
        if (!player)
            return true;

        if (ExitTestVehicleIfMounted(player, handler))
            return true;

        Creature* drake = player->SummonCreature(NPC_TEST_RUBY_DRAKE, player->GetPositionX(),
            player->GetPositionY(), player->GetPositionZ(), player->GetOrientation(),
            TEMPSUMMON_TIMED_DESPAWN, 600000);

        if (!drake)
        {
            handler->PSendSysMessage("Could not summon the test drake.");
            return true;
        }

        drake->SetDisableGravity(false);
        drake->SetHover(false);

        drake->RemoveVehicleKit();
        drake->CreateVehicleKit(VEHICLE_TEST_HIDE_PASSENGER, NPC_TEST_RUBY_DRAKE);

        player->EnterVehicle(drake, 0);

        // VEHICLE_SEAT_FLAG_HIDE_PASSENGER alone didn't hide the rider in-game (2026-08-14) -- the
        // WotLK client appears to always render the pilot's own model locally regardless of seat
        // flags. Shrinking the player to near-zero scale is a fully server-controlled fallback that
        // achieves the same visual result without depending on client-side seat behavior. Restored
        // to native scale in ExitTestVehicleIfMounted.
        player->SetObjectScale(0.01f);

        handler->PSendSysMessage("Boarded the test drake with the rider shrunk -- check whether you're effectively invisible on it.");
        return true;
    }

    // Boards the module's own cloned test dragon (901001, see dragon_legacy_vehicle_test.sql)
    // instead of the real Ruby Drake -- proves cloning into the module's reserved range works, and
    // that the display model can be freely swapped independent of the source it was cloned from
    // (this clone deliberately uses a different model than Ruby Drake's own).
    static bool HandleDragonTestVehicle4Command(ChatHandler* handler)
    {
        return ToggleTestVehicle(handler, NPC_TEST_DRAGON_CLONE,
            "Boarded the cloned test dragon (different model) -- check whether the swapped display renders correctly.");
    }
};

class DragonLegacy_Player : public PlayerScript
{
public:
    DragonLegacy_Player() : PlayerScript("DragonLegacy_Player") { }

    void OnPlayerBeforeLogout(Player* player) override
    {
        if (player->HasAura(SPELL_DRAGON_FORM))
            player->RemoveAurasDueToSpell(SPELL_DRAGON_FORM);
    }

    void OnPlayerDeleteFromDB(CharacterDatabaseTransaction trans, uint32 guid) override
    {
        // Otherwise a deleted character's guid can be recycled and the new character on that guid
        // would inherit an unlock it never earned.
        trans->Append("DELETE FROM mod_dragon_legacy_unlocked WHERE guid = {}", guid);
    }
};

void AddSC_mod_dragon_legacy()
{
    new DragonLegacy_NPC();
    new DragonLegacy_Player();
    new DragonLegacyCommandScript();
    RegisterSpellScript(spell_dragon_legacy_whelp_breath);
}