#pragma once

#include <map>
#include "DataSpec/DNACommon/DNACommon.hpp"
#include "DataSpec/DNACommon/ANCS.hpp"
#include "CMDLMaterials.hpp"
#include "CINF.hpp"
#include "CSKR.hpp"
#include "ANIM.hpp"
#include "EVNT.hpp"
#include "athena/FileReader.hpp"

namespace DataSpec::DNAMP1 {

struct ANCS : BigDNA {
  using CINFType = CINF;
  using CSKRType = CSKR;
  using ANIMType = ANIM;

  AT_DECL_DNA_YAML
  Value<atUint16> version;

  struct CharacterSet : BigDNA {
    AT_DECL_DNA_YAML
    Value<atUint16> version;
    Value<atUint32> characterCount;
    struct CharacterInfo : BigDNA {
      AT_DECL_DNA_YAML
      Delete expl;

      atUint32 idx;
      std::string name;
      UniqueID32 cmdl;
      UniqueID32 cskr;
      UniqueID32 cinf;

      struct Animation : BigDNA {
        AT_DECL_DNA_YAML
        Value<atUint32> animIdx;
        String<-1> strA;
        String<-1> strB;
      };
      std::vector<Animation> animations;

      struct PASDatabase : BigDNA {
        AT_DECL_DNA_YAML
        Value<atUint32> magic;
        Value<atUint32> animStateCount;
        Value<atUint32> defaultState;
        struct AnimState : BigDNA {
          AT_DECL_DNA_YAML
          Delete expl;
          atUint32 id;

          struct ParmInfo : BigDNA {
            AT_DECL_DNA_YAML
            Delete expl;
            enum class DataType { Int32 = 0, UInt32 = 1, Float = 2, Bool = 3, Enum = 4 };
            union Parm {
              atInt32 int32;
              atUint32 uint32;
              float float32;
              bool bool1;
              Parm() : int32(0) {}
              Parm(atInt32 val) : int32(val) {}
              Parm(atUint32 val) : uint32(val) {}
              Parm(float val) : float32(val) {}
              Parm(bool val) : bool1(val) {}
            };

            atUint32 parmType;
            atUint32 weightFunction;
            float weight;
            Parm range[2];
          };
          std::vector<ParmInfo> parmInfos;

          struct AnimInfo {
            atUint32 id;
            std::vector<ParmInfo::Parm> parmVals;
          };
          std::vector<AnimInfo> animInfos;
        };
        Vector<AnimState, AT_DNA_COUNT(animStateCount)> animStates;
      } pasDatabase;

      struct ParticleResData {
        std::vector<UniqueID32> part;
        std::vector<UniqueID32> swhc;
        std::vector<UniqueID32> unk;
        std::vector<UniqueID32> elsc;
      } partResData;

      atUint32 unk1 = 0;

      struct ActionAABB : BigDNA {
        AT_DECL_DNA_YAML
        String<-1> name;
        Value<atVec3f> aabb[2];
      };
      std::vector<ActionAABB> animAABBs;

      struct Effect : BigDNA {
        AT_DECL_DNA_YAML
        String<-1> name;
        Value<atUint32> compCount;
        struct EffectComponent : BigDNA {
          AT_DECL_DNA_YAML
          String<-1> name;
          DNAFourCC type;
          UniqueID32 id;
          String<-1> locator;
          Value<float> scale;
          Value<atUint32> parentMode;
          Value<atUint32> flags;
        };
        Vector<EffectComponent, AT_DNA_COUNT(compCount)> comps;
      };
      std::vector<Effect> effects;

      UniqueID32Zero cmdlIce;
      UniqueID32Zero cskrIce;

      std::vector<atUint32> animIdxs;
    };
    Vector<CharacterInfo, AT_DNA_COUNT(characterCount)> characters;
  } characterSet;

  struct AnimationSet : BigDNA {
    AT_DECL_DNA_YAML
    Delete expl;

    struct MetaAnimPrimitive;
    struct IMetaAnim : BigDNAVYaml {
      Delete expl;
      enum class Type { Primitive = 0, Blend = 1, PhaseBlend = 2, Random = 3, Sequence = 4 } m_type;
      const char* m_typeStr;
      IMetaAnim(Type type, const char* typeStr) : m_type(type), m_typeStr(typeStr) {}
      virtual void gatherPrimitives(PAKRouter<PAKBridge>* pakRouter,
                                    std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) = 0;
      virtual bool enumeratePrimitives(const std::function<bool(MetaAnimPrimitive& prim)>& func) = 0;
    };
    struct MetaAnimFactory : BigDNA {
      AT_DECL_EXPLICIT_DNA_YAML
      std::unique_ptr<IMetaAnim> m_anim;
    };
    struct MetaAnimPrimitive : IMetaAnim {
      AT_DECL_DNA_YAMLV
      MetaAnimPrimitive() : IMetaAnim(Type::Primitive, "Primitive") {}

      UniqueID32 animId;
      Value<atUint32> animIdx;
      String<-1> animName;
      Value<float> unk1;
      Value<atUint32> unk2;

      void gatherPrimitives(PAKRouter<PAKBridge>* pakRouter,
                            std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) override;

      bool enumeratePrimitives(const std::function<bool(MetaAnimPrimitive& prim)>& func) override {
        return func(*this);
      }
    };
    struct MetaAnimBlend : IMetaAnim {
      MetaAnimBlend() : IMetaAnim(Type::Blend, "Blend") {}
      AT_DECL_DNA_YAMLV
      MetaAnimFactory animA;
      MetaAnimFactory animB;
      Value<float> unkFloat;
      Value<atUint8> unk;

      void gatherPrimitives(PAKRouter<PAKBridge>* pakRouter,
                            std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) override {
        animA.m_anim->gatherPrimitives(pakRouter, out);
        animB.m_anim->gatherPrimitives(pakRouter, out);
      }

      bool enumeratePrimitives(const std::function<bool(MetaAnimPrimitive& prim)>& func) override {
        if (!animA.m_anim->enumeratePrimitives(func))
          return false;
        if (!animB.m_anim->enumeratePrimitives(func))
          return false;
        return true;
      }
    };
    struct MetaAnimPhaseBlend : IMetaAnim {
      MetaAnimPhaseBlend() : IMetaAnim(Type::PhaseBlend, "PhaseBlend") {}
      AT_DECL_DNA_YAMLV
      MetaAnimFactory animA;
      MetaAnimFactory animB;
      Value<float> unkFloat;
      Value<atUint8> unk;

      void gatherPrimitives(PAKRouter<PAKBridge>* pakRouter,
                            std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) override {
        animA.m_anim->gatherPrimitives(pakRouter, out);
        animB.m_anim->gatherPrimitives(pakRouter, out);
      }

      bool enumeratePrimitives(const std::function<bool(MetaAnimPrimitive& prim)>& func) override {
        if (!animA.m_anim->enumeratePrimitives(func))
          return false;
        if (!animB.m_anim->enumeratePrimitives(func))
          return false;
        return true;
      }
    };
    struct MetaAnimRandom : IMetaAnim {
      MetaAnimRandom() : IMetaAnim(Type::Random, "Random") {}
      AT_DECL_DNA_YAMLV
      Value<atUint32> animCount;
      struct Child : BigDNA {
        AT_DECL_DNA
        MetaAnimFactory anim;
        Value<atUint32> probability;
      };
      Vector<Child, AT_DNA_COUNT(animCount)> children;

      void gatherPrimitives(PAKRouter<PAKBridge>* pakRouter,
                            std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) override {
        for (const auto& child : children)
          child.anim.m_anim->gatherPrimitives(pakRouter, out);
      }

      bool enumeratePrimitives(const std::function<bool(MetaAnimPrimitive& prim)>& func) override {
        for (auto& child : children)
          if (!child.anim.m_anim->enumeratePrimitives(func))
            return false;
        return true;
      }
    };
    struct MetaAnimSequence : IMetaAnim {
      MetaAnimSequence() : IMetaAnim(Type::Sequence, "Sequence") {}
      AT_DECL_DNA_YAMLV
      Value<atUint32> animCount;
      Vector<MetaAnimFactory, AT_DNA_COUNT(animCount)> children;

      void gatherPrimitives(PAKRouter<PAKBridge>* pakRouter,
                            std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) override {
        for (const auto& child : children)
          child.m_anim->gatherPrimitives(pakRouter, out);
      }

      bool enumeratePrimitives(const std::function<bool(MetaAnimPrimitive& prim)>& func) override {
        for (auto& child : children)
          if (!child.m_anim->enumeratePrimitives(func))
            return false;
        return true;
      }
    };

    struct Animation : BigDNA {
      AT_DECL_DNA_YAML
      String<-1> name;
      MetaAnimFactory metaAnim;
    };
    std::vector<Animation> animations;

    struct IMetaTrans : BigDNAVYaml {
      Delete expl;
      enum class Type {
        MetaAnim = 0,
        Trans = 1,
        PhaseTrans = 2,
        NoTrans = 3,
      } m_type;
      const char* m_typeStr;
      IMetaTrans(Type type, const char* typeStr) : m_type(type), m_typeStr(typeStr) {}
      virtual void gatherPrimitives(PAKRouter<PAKBridge>* pakRouter,
                                    std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) {}
      virtual bool enumeratePrimitives(const std::function<bool(MetaAnimPrimitive& prim)>& func) { return true; }
    };
    struct MetaTransFactory : BigDNA {
      AT_DECL_DNA_YAML
      Delete expl;
      std::unique_ptr<IMetaTrans> m_trans;
    };
    struct MetaTransMetaAnim : IMetaTrans {
      MetaTransMetaAnim() : IMetaTrans(Type::MetaAnim, "MetaAnim") {}
      AT_DECL_DNA_YAMLV
      MetaAnimFactory anim;

      void gatherPrimitives(PAKRouter<PAKBridge>* pakRouter,
                            std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) override {
        anim.m_anim->gatherPrimitives(pakRouter, out);
      }

      bool enumeratePrimitives(const std::function<bool(MetaAnimPrimitive& prim)>& func) override {
        return anim.m_anim->enumeratePrimitives(func);
      }
    };
    struct MetaTransTrans : IMetaTrans {
      MetaTransTrans() : IMetaTrans(Type::Trans, "Trans") {}
      AT_DECL_DNA_YAMLV
      Value<float> transDurTime;
      Value<atUint32> transDurTimeMode;
      Value<bool> unk2;
      Value<bool> runA;
      Value<atUint32> flags;
    };
    struct MetaTransPhaseTrans : IMetaTrans {
      MetaTransPhaseTrans() : IMetaTrans(Type::PhaseTrans, "PhaseTrans") {}
      AT_DECL_DNA_YAMLV
      Value<float> transDurTime;
      Value<atUint32> transDurTimeMode;
      Value<bool> unk2;
      Value<bool> runA;
      Value<atUint32> flags;
    };

    struct Transition : BigDNA {
      AT_DECL_DNA_YAML
      Value<atUint32> unk;
      Value<atUint32> animIdxA;
      Value<atUint32> animIdxB;
      MetaTransFactory metaTrans;
    };
    std::vector<Transition> transitions;
    MetaTransFactory defaultTransition;

    struct AdditiveAnimationInfo : BigDNA {
      AT_DECL_DNA_YAML
      Value<atUint32> animIdx;
      Value<float> fadeInDur;
      Value<float> fadeOutDur;
    };
    std::vector<AdditiveAnimationInfo> additiveAnims;

    float additiveDefaultFadeInDur = 0.0;
    float additiveDefaultFadeOutDur = 0.0;

    struct HalfTransition : BigDNA {
      AT_DECL_DNA_YAML
      Value<atUint32> animIdx;
      MetaTransFactory metaTrans;
    };
    std::vector<HalfTransition> halfTransitions;

    struct AnimationResources : BigDNA {
      AT_DECL_DNA_YAML
      UniqueID32 animId;
      UniqueID32 evntId;
    };
    std::vector<AnimationResources> animResources;
  } animationSet;

  void getCharacterResInfo(std::vector<DNAANCS::CharacterResInfo<UniqueID32>>& out) const {
    out.clear();
    out.reserve(characterSet.characters.size());
    for (const CharacterSet::CharacterInfo& ci : characterSet.characters) {
      out.emplace_back();
      DNAANCS::CharacterResInfo<UniqueID32>& chOut = out.back();
      chOut.name = ci.name;
      chOut.cmdl = ci.cmdl;
      chOut.cskr = ci.cskr;
      chOut.cinf = ci.cinf;

      if (ci.cmdlIce.isValid())
        chOut.overlays.emplace_back("ICE", std::make_pair(ci.cmdlIce, ci.cskrIce));
    }
  }

  void getAnimationResInfo(PAKRouter<PAKBridge>* pakRouter,
                           std::map<atUint32, DNAANCS::AnimationResInfo<UniqueID32>>& out) const {
    out.clear();
    for (const AnimationSet::Animation& ai : animationSet.animations)
      if (AnimationSet::IMetaAnim* anim = ai.metaAnim.m_anim.get())
        anim->gatherPrimitives(pakRouter, out);
    for (const AnimationSet::Transition& ti : animationSet.transitions)
      if (AnimationSet::IMetaTrans* trans = ti.metaTrans.m_trans.get())
        trans->gatherPrimitives(pakRouter, out);
    if (AnimationSet::IMetaTrans* trans = animationSet.defaultTransition.m_trans.get())
      trans->gatherPrimitives(pakRouter, out);
  }

  void enumeratePrimitives(const std::function<bool(AnimationSet::MetaAnimPrimitive& prim)>& func) {
    for (const AnimationSet::Animation& ai : animationSet.animations)
      if (AnimationSet::IMetaAnim* anim = ai.metaAnim.m_anim.get())
        anim->enumeratePrimitives(func);
    for (const AnimationSet::Transition& ti : animationSet.transitions)
      if (AnimationSet::IMetaTrans* trans = ti.metaTrans.m_trans.get())
        trans->enumeratePrimitives(func);
    if (AnimationSet::IMetaTrans* trans = animationSet.defaultTransition.m_trans.get())
      trans->enumeratePrimitives(func);
  }

  void gatherDependencies(std::vector<hecl::ProjectPath>& pathsOut, int charIdx) const {
    auto doCi = [&](const CharacterSet::CharacterInfo& ci) {
      for (const auto& id : ci.partResData.part)
        g_curSpec->flattenDependencies(id, pathsOut);
      for (const auto& id : ci.partResData.swhc)
        g_curSpec->flattenDependencies(id, pathsOut);
      for (const auto& id : ci.partResData.unk)
        g_curSpec->flattenDependencies(id, pathsOut);
      for (const auto& id : ci.partResData.elsc)
        g_curSpec->flattenDependencies(id, pathsOut);
    };
    if (charIdx < 0)
      for (const CharacterSet::CharacterInfo& ci : characterSet.characters)
        doCi(ci);
    else if (charIdx < characterSet.characters.size())
      doCi(characterSet.characters[charIdx]);
  }

  static bool Extract(const SpecBase& dataSpec, PAKEntryReadStream& rs, const hecl::ProjectPath& outPath,
                      PAKRouter<PAKBridge>& pakRouter, const PAK::Entry& entry, bool force, hecl::blender::Token& btok,
                      std::function<void(const hecl::SystemChar*)> fileChanged);

  static bool Cook(const hecl::ProjectPath& outPath, const hecl::ProjectPath& inPath, const DNAANCS::Actor& actor);

  static bool CookCSKR(const hecl::ProjectPath& outPath, const hecl::ProjectPath& inPath, const DNAANCS::Actor& actor,
                       const std::function<bool(const hecl::ProjectPath& modelPath)>& modelCookFunc);
  static bool CookCSKRPC(const hecl::ProjectPath& outPath, const hecl::ProjectPath& inPath, const DNAANCS::Actor& actor,
                         const std::function<bool(const hecl::ProjectPath& modelPath)>& modelCookFunc);

  static bool CookANIM(const hecl::ProjectPath& outPath, const hecl::ProjectPath& inPath, const DNAANCS::Actor& actor,
                       hecl::blender::DataStream& ds, bool pc);
};

} // namespace DataSpec::DNAMP1
