#include "common.h"

// One generated-quest objective: what to do, on what target, how much, and progress so far.
typedef struct {
    char type[MAX_GEN_NAME];    // e.g. "Kill" — one of the built-in quest types
    char target[MAX_GEN_NAME];  // mob / item / location name this objective is about (may be empty)
    int  amount;                // count to reach, or a time value depending on the type
    int  progress;              // current progress toward amount
    char customText[128];       // type=="Custom" only: the whole objective line, written by hand
} Objective;

typedef enum { DIFF_EASY, DIFF_NORMAL, DIFF_HARD, DIFF_ELITE, DIFF_LEGENDARY, DIFFICULTY_COUNT } Difficulty;

typedef struct {
    char name[MAX_QNAME]; char desc[MAX_QDESC]; int cp, xp; bool done; int type;

    // ---- Generator extension: zero/empty for manually-created quests ----
    bool       generated;
    int        level;        // recommended character level — XP reward scales from this
    Difficulty difficulty;
    Objective  objectives[MAX_OBJECTIVES];
    int        objectiveCount;
    char       rewardItems[MAX_REWARD_ITEMS][MAX_GEN_NAME];
    int        rewardItemCount;
} Quest;

// Quest state
static Quest quests[MAX_QUESTS];
static int   questCount = 0;
static int   earnedCP   = 0, earnedXP = 0;
static int   totalCP    = 0;

// ---- Quest Generator config (loaded from data/*.txt, one entry per line) ----
typedef struct {
    char items[MAX_GEN_ENTRIES][MAX_GEN_NAME];
    int  count;
} NameList;

static NameList genMobs, genItems, genLocations, genTitles, genBosses;
static NameList genFarming, genFishing, genMining, genNpcNames;
static NameList genDefendCombined;   // Defend's pool: Locations + NPC names, rebuilt on each read
static int      genQuestCounter = 0;   // running number used in generated quest names ("Quest 7: ...")

// A quest type's shape decides which pool its target is drawn from, and whether
// it needs a target+amount, just a target, a time value, or (for "Custom") a
// fully hand-written line. The type list itself is a small built-in table
// (see LoadQuestTypeDefs) — it just points at the pool .txt files below.
typedef enum { QSHAPE_TARGET_AMOUNT, QSHAPE_TARGET_ONLY, QSHAPE_TIME, QSHAPE_CUSTOM } QuestShape;
typedef enum { QPOOL_MOB, QPOOL_ITEM, QPOOL_LOCATION, QPOOL_BOSS,
               QPOOL_FARMING, QPOOL_FISHING, QPOOL_MINING, QPOOL_NPC,
               QPOOL_DEFEND,   // Defend's target: Locations + NPC names combined
               QPOOL_NONE } QuestPool;

typedef struct {
    char       name[MAX_GEN_NAME];
    QuestShape shape;
    QuestPool  pool;
} QuestTypeDef;

static QuestTypeDef genQuestTypeDefs[MAX_GEN_ENTRIES];
static int          genQuestTypeDefCount = 0;

int          quest_tab    = 0;
static int   quest_scroll = 0;
int          quest_view   = -1;
bool         quest_create = false;
static char  q_name[MAX_QNAME] = {0};
static char  q_desc[MAX_QDESC] = {0};
static char  q_cp  [MAX_QNUM]  = {0};
static char  q_xp  [MAX_QNUM]  = {0};
static int   q_focus    = 0;
static float q_mscale   = 0.f;
static float q_bs       = 0.f;

// Quest text input with backspace repeat
static void TypeInto(char *buf, int maxLen, bool numeric, float dt)
{
    int key=GetCharPressed();
    while(key>0){
        bool ok = numeric?(key>='0'&&key<='9'):(key>=32&&key<126);
        if(ok&&(int)strlen(buf)<maxLen-1){ int l=(int)strlen(buf); buf[l]=(char)key; buf[l+1]='\0'; }
        key=GetCharPressed();
    }
    if     (IsKeyPressed(KEY_BACKSPACE)){ int l=(int)strlen(buf); if(l>0)buf[l-1]='\0'; q_bs=0; }
    else if(IsKeyDown(KEY_BACKSPACE))   { q_bs+=dt; if(q_bs>0.5f){ q_bs=0.42f; int l=(int)strlen(buf); if(l>0)buf[l-1]='\0'; } }
    else q_bs=0;
}

// Quest drawing helpers
static void QBadge(int x, int y, const char *lbl, int val, Color bg, Color fg)
{
    char buf[32]; snprintf(buf,32,"%s %d",lbl,val);
    int w=MeasureText(buf,11)+10;
    DrawRectangleRounded     ((Rectangle){(float)x,(float)y,(float)w,18},0.5f,6,bg);
    DrawRectangleRoundedLines((Rectangle){(float)x,(float)y,(float)w,18},0.5f,6,fg);
    DrawText(buf,x+5,y+3,11,fg);
}

static void QButton(Rectangle r, const char *txt, Color bg, Color fg, bool hov)
{
    Color b2=hov?(Color){bg.r+20,bg.g+15,bg.b+30,bg.a}:bg;
    DrawRectangleRounded(r,0.35f,6,b2);
    DrawRectangleRoundedLines(r,0.35f,6,fg);
    int tw=MeasureText(txt,12);
    DrawText(txt,(int)(r.x+r.width/2-tw/2),(int)(r.y+r.height/2-6),12,fg);
}

static void QInputBox(Rectangle r, const char *label, const char *val, bool focused)
{
    DrawText(label,(int)r.x,(int)r.y-16,12,Q_TDIM);
    DrawRectangleRec(r,Q_INBG);
    DrawRectangleLinesEx(r,1.5f,focused?C_GOLD:C_BORDER);
    if(focused)
        DrawRectangleLinesEx((Rectangle){r.x-1,r.y-1,r.width+2,r.height+2},1,(Color){C_GOLD.r,C_GOLD.g,C_GOLD.b,70});
    char disp[256]; strncpy(disp,val,255); disp[255]='\0';
    while(MeasureText(disp,14)>(int)r.width-12&&strlen(disp)>0) disp[strlen(disp)-1]='\0';
    DrawText(disp,(int)r.x+6,(int)(r.y+r.height/2-7),14,C_TEXT);
    if(focused&&(int)(GetTime()*2)%2==0){
        int cx=(int)r.x+6+MeasureText(disp,14);
        DrawLine(cx,(int)r.y+5,cx,(int)r.y+(int)r.height-5,C_GOLD);
    }
}

static int QTextWrap(const char *txt, int x, int y, int fs, int maxW, Color col)
{
    char word[64],line[512]={0};
    int lH=fs+4, yy=y;
    const char *p=txt;
    while(*p){
        int wi=0;
        while(*p&&*p!=' '&&*p!='\n'&&wi<63) word[wi++]=*p++;
        word[wi]='\0'; if(*p==' ')p++;
        char test[512];
        if(!strlen(line)) snprintf(test,512,"%s",word);
        else              snprintf(test,512,"%s %s",line,word);
        if(MeasureText(test,fs)>maxW&&strlen(line)>0){
            DrawText(line,x,yy,fs,col); yy+=lH; strcpy(line,word);
        } else strcpy(line,test);
        if(*p=='\n'||*(p-1)=='\n'){
            DrawText(line,x,yy,fs,col); yy+=lH; line[0]='\0'; if(*p=='\n')p++;
        }
    }
    if(strlen(line)){ DrawText(line,x,yy,fs,col); yy+=lH; }
    return yy-y;
}

// Save/load quests
static void QuestSave(void)
{
    FILE *f = fopen(QUEST_SAVE_FILE,"w");
    if(!f){ ShowNotif("ERROR: Could not save quests!"); return; }
    fprintf(f,"# Aetheria Quest Journal\nearned_cp=%d\nearned_xp=%d\ntotal_cp=%d\n\n",
            earnedCP, earnedXP, totalCP);
    for(int i=0;i<questCount;i++){
        Quest *q=&quests[i];
        fprintf(f,"QUEST\nname=%s\ndesc=%s\ncp=%d\nxp=%d\ndone=%d\ntype=%d\n",
                q->name, q->desc, q->cp, q->xp, q->done?1:0, q->type);
        fprintf(f,"gen=%d\nlvl=%d\ndiff=%d\nobjs=%d\n",q->generated?1:0,q->level,(int)q->difficulty,q->objectiveCount);
        for(int j=0;j<q->objectiveCount;j++)
            fprintf(f,"o%dt=%s\no%dg=%s\no%da=%d\no%dp=%d\no%dc=%s\n",
                    j,q->objectives[j].type, j,q->objectives[j].target,
                    j,q->objectives[j].amount, j,q->objectives[j].progress,
                    j,q->objectives[j].customText);
        fprintf(f,"ritems=%d\n",q->rewardItemCount);
        for(int j=0;j<q->rewardItemCount;j++) fprintf(f,"r%d=%s\n",j,q->rewardItems[j]);
        fprintf(f,"---\n");
    }
    fclose(f);
}

void QuestLoad(void)
{
    FILE *f = fopen(QUEST_SAVE_FILE,"r");
    if(!f) return;
    char line[512]; Quest *cur=NULL;
    while(fgets(line,sizeof(line),f)){
        int n=(int)strlen(line);
        while(n>0&&(line[n-1]=='\n'||line[n-1]=='\r')) line[--n]='\0';
        if     (strcmp(line,"QUEST")==0){ if(questCount<MAX_QUESTS){ cur=&quests[questCount++]; memset(cur,0,sizeof(Quest)); } }
        else if(cur&&strncmp(line,"name=",5)==0) strncpy(cur->name,line+5,MAX_QNAME-1);
        else if(cur&&strncmp(line,"desc=",5)==0) strncpy(cur->desc,line+5,MAX_QDESC-1);
        else if(cur&&strncmp(line,"cp=",3)==0)   cur->cp=atoi(line+3);
        else if(cur&&strncmp(line,"xp=",3)==0)   cur->xp=atoi(line+3);
        else if(cur&&strncmp(line,"done=",5)==0) cur->done=(atoi(line+5)==1);
        else if(cur&&strncmp(line,"type=",5)==0) cur->type=atoi(line+5);
        else if(cur&&strncmp(line,"gen=",4)==0)  cur->generated=(atoi(line+4)==1);
        else if(cur&&strncmp(line,"lvl=",4)==0)  cur->level=atoi(line+4);
        else if(cur&&strncmp(line,"diff=",5)==0) cur->difficulty=(Difficulty)atoi(line+5);
        else if(cur&&strncmp(line,"objs=",5)==0) cur->objectiveCount=atoi(line+5);
        else if(cur&&strncmp(line,"ritems=",7)==0) cur->rewardItemCount=atoi(line+7);
        else if(cur&&line[0]=='o'&&isdigit((unsigned char)line[1])){
            int idx=atoi(line+1);
            const char *eq=strchr(line,'=');
            char field=0; for(const char *p=line;p<eq;p++) if(!isdigit((unsigned char)*p)&&*p!='o') field=*p;
            if(idx>=0&&idx<MAX_OBJECTIVES&&eq){
                if(field=='t')      strncpy(cur->objectives[idx].type,eq+1,MAX_GEN_NAME-1);
                else if(field=='g') strncpy(cur->objectives[idx].target,eq+1,MAX_GEN_NAME-1);
                else if(field=='a') cur->objectives[idx].amount=atoi(eq+1);
                else if(field=='p') cur->objectives[idx].progress=atoi(eq+1);
                else if(field=='c') strncpy(cur->objectives[idx].customText,eq+1,sizeof(cur->objectives[idx].customText)-1);
            }
        }
        else if(cur&&line[0]=='r'&&isdigit((unsigned char)line[1])){
            int idx=atoi(line+1);
            const char *eq=strchr(line,'=');
            if(idx>=0&&idx<MAX_REWARD_ITEMS&&eq) strncpy(cur->rewardItems[idx],eq+1,MAX_GEN_NAME-1);
        }
        else if(strcmp(line,"---")==0) cur=NULL;
    }
    fclose(f);
}

// ---- Quest Generator ---------------------------------------------------------

static int LoadNameListFile(const char *path, NameList *list)
{
    list->count=0;
    FILE *f=fopen(path,"r");
    if(!f) return 0;
    char line[128];
    while(list->count<MAX_GEN_ENTRIES && fgets(line,sizeof(line),f)){
        int n=(int)strlen(line);
        while(n>0 && (line[n-1]=='\n'||line[n-1]=='\r'||line[n-1]==' '||line[n-1]=='\t')) line[--n]='\0';
        char *p=line; while(*p==' '||*p=='\t') p++;
        if(*p=='\0'||*p=='#') continue;
        strncpy(list->items[list->count],p,MAX_GEN_NAME-1);
        list->items[list->count][MAX_GEN_NAME-1]='\0';
        list->count++;
    }
    fclose(f);
    return list->count;
}

static void SeedNameListFile(const char *path, const char *label, const char **defaults, int n)
{
    FILE *f=fopen(path,"w");
    if(!f) return;
    fprintf(f,"# Aetheria %s\n# One entry per line. Lines starting with # are ignored.\n#\n",label);
    for(int i=0;i<n;i++) fprintf(f,"%s\n",defaults[i]);
    fclose(f);
}

// Loads a generator list, seeding the file with `defaults` the first time it doesn't exist.
static void EnsureGenList(const char *path, const char *label, NameList *list, const char **defaults, int nDefaults)
{
    if(LoadNameListFile(path,list)>0) return;
    SeedNameListFile(path,label,defaults,nDefaults);
    for(int i=0;i<nDefaults && i<MAX_GEN_ENTRIES;i++){
        strncpy(list->items[i],defaults[i],MAX_GEN_NAME-1);
        list->items[i][MAX_GEN_NAME-1]='\0';
    }
    list->count = nDefaults<MAX_GEN_ENTRIES ? nDefaults : MAX_GEN_ENTRIES;
}

// The quest type list itself lives in code (no txt file) — it's the "shape" of
// each type (target+amount / just a target / a duration / fully custom text)
// that matters, and that's a code-level decision. Each type still points at
// one of the pool .txt files below (mobs/items/locations/boss), so growing
// mobs.txt etc. still needs no code change — only adding a brand-new TYPE does.
static void LoadQuestTypeDefs(void)
{
    static const struct { const char *name; QuestShape shape; QuestPool pool; } defs[] = {
        {"Kill",    QSHAPE_TARGET_AMOUNT, QPOOL_MOB},
        {"Collect", QSHAPE_TARGET_AMOUNT, QPOOL_ITEM},
        {"Visit",   QSHAPE_TARGET_ONLY,   QPOOL_LOCATION},
        {"Deliver", QSHAPE_TARGET_AMOUNT, QPOOL_ITEM},
        {"Defend",  QSHAPE_TIME,          QPOOL_DEFEND},
        {"Escort to",  QSHAPE_TARGET_ONLY,   QPOOL_LOCATION},
        {"Boss",    QSHAPE_TARGET_AMOUNT, QPOOL_BOSS},
        {"Mine",    QSHAPE_TARGET_AMOUNT, QPOOL_MINING},
        {"Fish",    QSHAPE_TARGET_AMOUNT, QPOOL_FISHING},
        {"Farm",    QSHAPE_TARGET_AMOUNT, QPOOL_FARMING},
        {"Custom",  QSHAPE_CUSTOM,        QPOOL_NONE},
    };
    genQuestTypeDefCount=(int)(sizeof(defs)/sizeof(defs[0]));
    if(genQuestTypeDefCount>MAX_GEN_ENTRIES) genQuestTypeDefCount=MAX_GEN_ENTRIES;
    for(int i=0;i<genQuestTypeDefCount;i++){
        QuestTypeDef *d=&genQuestTypeDefs[i];
        strncpy(d->name,defs[i].name,MAX_GEN_NAME-1); d->name[MAX_GEN_NAME-1]='\0';
        d->shape=defs[i].shape;
        d->pool =defs[i].pool;
    }
}

void QuestGenInit(void)
{
    static const char *defMobs[]      = {"Zombie","Skeleton","Goblin","Wolf","Dragon"};
    static const char *defItems[]     = {"Wood","Iron Ore","Crystal","Herb","Bone"};
    static const char *defLocations[] = {"Forest","Cave","Castle","Village","Dungeon"};
    static const char *defTitles[]    = {"Goblin Extermination","Lost Supplies","Ancient Discovery",
                                          "Dangerous Hunt","Forest Patrol","Crystal Recovery"};
    static const char *defBosses[]    = {"Ancient Dragon","Lich King","Demon Overlord","Kraken","Stone Titan"};
    static const char *defFarming[]   = {"Wheat","Carrot","Potato","Corn","Pumpkin","Barley","Cotton","Sugarcane","Melon","Hops"};
    static const char *defFishing[]   = {"Salmon","Trout","Bass","Carp","Cod","Pike","Eel","Catfish","Herring","Tuna"};
    static const char *defMining[]    = {"Iron Ore","Copper Ore","Gold Ore","Coal","Silver Ore","Tin Ore","Mithril Ore","Obsidian","Granite","Quartz"};
    static const char *defNpcNames[]  = {"Aldric","Brynn","Corwin","Daria","Elric","Freya","Garron","Halia","Ingrid","Joran"};

    // Auto-clean every list file (drop duplicate lines, sort alphabetically) before loading it.
    DedupeSortTextFile(QGEN_MOBS_FILE);
    DedupeSortTextFile(QGEN_ITEMS_FILE);
    DedupeSortTextFile(QGEN_LOCATIONS_FILE);
    DedupeSortTextFile(QGEN_BOSS_FILE);
    DedupeSortTextFile(QGEN_TITLES_FILE);
    DedupeSortTextFile(QGEN_FARMING_FILE);
    DedupeSortTextFile(QGEN_FISHING_FILE);
    DedupeSortTextFile(QGEN_MINING_FILE);
    DedupeSortTextFile(QGEN_NPC_NAMES_FILE);

    EnsureGenList(QGEN_MOBS_FILE,     "Quest Generator - Mobs",      &genMobs,      defMobs,      5);
    EnsureGenList(QGEN_ITEMS_FILE,    "Quest Generator - Items",     &genItems,     defItems,     5);
    EnsureGenList(QGEN_LOCATIONS_FILE,"Quest Generator - Locations", &genLocations, defLocations, 5);
    EnsureGenList(QGEN_BOSS_FILE,     "Quest Generator - Bosses",    &genBosses,    defBosses,    5);
    EnsureGenList(QGEN_FARMING_FILE,  "Quest Generator - Farming Items", &genFarming, defFarming, 10);
    EnsureGenList(QGEN_FISHING_FILE,  "Quest Generator - Fishing Items", &genFishing, defFishing, 10);
    EnsureGenList(QGEN_MINING_FILE,   "Quest Generator - Mining Items",  &genMining,  defMining,  10);
    EnsureGenList(QGEN_NPC_NAMES_FILE,"Quest Generator - NPC Names",     &genNpcNames,defNpcNames,10);
    LoadQuestTypeDefs();
    EnsureGenList(QGEN_TITLES_FILE,   "Quest Generator - Titles",    &genTitles,    defTitles,    6);

    genQuestCounter = questCount;
}

static const char *DifficultyName(Difficulty d)
{
    static const char *names[DIFFICULTY_COUNT] = {"Easy","Normal","Hard","Elite","Legendary"};
    return names[d];
}

static Color DifficultyColor(Difficulty d)
{
    switch(d){
        case DIFF_EASY:      return C_GREEN_L;
        case DIFF_NORMAL:    return C_BLUE_L;
        case DIFF_HARD:      return Q_CP;
        case DIFF_ELITE:     return C_RED_L;
        default:             return Q_GPALE; // Legendary
    }
}

// Higher difficulty scales amounts and rewards up.
static float DifficultyFactor(Difficulty d){ return 1.f + (float)d*0.6f; }

// Looks up a type's shape/pool by name. Falls back to a generic target+amount
// for a type that isn't in quest_types.txt (e.g. an older saved objective).
static void GetTypeShape(const char *type, QuestShape *shape, QuestPool *pool)
{
    for(int i=0;i<genQuestTypeDefCount;i++){
        if(strcmp(genQuestTypeDefs[i].name,type)==0){ *shape=genQuestTypeDefs[i].shape; *pool=genQuestTypeDefs[i].pool; return; }
    }
    *shape=QSHAPE_TARGET_AMOUNT; *pool=QPOOL_ITEM;
}

// Defend's target picks from Locations and NPC names together - rebuilt on each
// read so it always reflects the current contents of both source lists.
static NameList *DefendCombinedList(void)
{
    genDefendCombined.count=0;
    for(int i=0;i<genLocations.count && genDefendCombined.count<MAX_GEN_ENTRIES;i++)
        strncpy(genDefendCombined.items[genDefendCombined.count++],genLocations.items[i],MAX_GEN_NAME-1);
    for(int i=0;i<genNpcNames.count && genDefendCombined.count<MAX_GEN_ENTRIES;i++)
        strncpy(genDefendCombined.items[genDefendCombined.count++],genNpcNames.items[i],MAX_GEN_NAME-1);
    return &genDefendCombined;
}

static NameList *PoolList(QuestPool pool)
{
    switch(pool){
        case QPOOL_MOB:      return &genMobs;
        case QPOOL_ITEM:     return &genItems;
        case QPOOL_LOCATION: return &genLocations;
        case QPOOL_BOSS:     return &genBosses;
        case QPOOL_FARMING:  return &genFarming;
        case QPOOL_FISHING:  return &genFishing;
        case QPOOL_MINING:   return &genMining;
        case QPOOL_NPC:      return &genNpcNames;
        case QPOOL_DEFEND:   return DefendCombinedList();
        default:             return NULL;
    }
}

static const char *PoolPick(QuestPool pool)
{
    NameList *l=PoolList(pool);
    if(!l || l->count==0) return "Something";
    return l->items[rand()%l->count];
}

static const char *PoolFilePath(QuestPool pool)
{
    switch(pool){
        case QPOOL_MOB:      return QGEN_MOBS_FILE;
        case QPOOL_ITEM:     return QGEN_ITEMS_FILE;
        case QPOOL_LOCATION: return QGEN_LOCATIONS_FILE;
        case QPOOL_BOSS:     return QGEN_BOSS_FILE;
        case QPOOL_FARMING:  return QGEN_FARMING_FILE;
        case QPOOL_FISHING:  return QGEN_FISHING_FILE;
        case QPOOL_MINING:   return QGEN_MINING_FILE;
        // Defend adds new entries to the NPC list specifically - Locations can
        // already be grown via the Visit/Escort types.
        case QPOOL_NPC: case QPOOL_DEFEND: return QGEN_NPC_NAMES_FILE;
        default: return NULL;
    }
}

// Adds a brand-new entry to a pool (in memory and to its .txt file), so the
// target picker can grow the list on the spot instead of requiring a file edit.
static bool EqualsCI(const char *a, const char *b)
{
    while(*a && *b){
        char ca=*a, cb=*b;
        if(ca>='A'&&ca<='Z') ca=(char)(ca+32);
        if(cb>='A'&&cb<='Z') cb=(char)(cb+32);
        if(ca!=cb) return false;
        a++; b++;
    }
    return *a==*b;
}

static int PoolFindIndex(QuestPool pool, const char *name)
{
    NameList *l=PoolList(pool);
    if(!l) return -1;
    for(int i=0;i<l->count;i++) if(EqualsCI(l->items[i],name)) return i;
    return -1;
}

// Appends to the pool's file, then re-cleans it (dedupe + alphabetical sort) and
// reloads the in-memory list from the cleaned file, so the two never drift apart.
static bool PoolAppend(QuestPool pool, const char *name)
{
    // Defend's list is a rebuilt-on-read merge (not a real backing list), so
    // reload straight into the NPC list it actually writes to.
    NameList *l = (pool==QPOOL_DEFEND) ? &genNpcNames : PoolList(pool);
    const char *path=PoolFilePath(pool);
    if(!l || !path || strlen(name)==0) return false;
    if(l->count>=MAX_GEN_ENTRIES) return false;

    FILE *f=fopen(path,"a");
    if(f){ fprintf(f,"%s\n",name); fclose(f); }

    DedupeSortTextFile(path);
    LoadNameListFile(path,l);
    return true;
}

static void GenerateObjective(Objective *o, Difficulty diff)
{
    memset(o,0,sizeof(*o));
    if(genQuestTypeDefCount==0) strncpy(o->type,"Kill",MAX_GEN_NAME-1);
    else {
        // "Custom" is hand-written only — never picked by the random generator.
        int idx;
        int tries=0;
        do { idx=rand()%genQuestTypeDefCount; tries++; }
        while(strcmp(genQuestTypeDefs[idx].name,"Custom")==0 && tries<20 && genQuestTypeDefCount>1);
        strncpy(o->type,genQuestTypeDefs[idx].name,MAX_GEN_NAME-1);
    }

    QuestShape shape; QuestPool pool;
    GetTypeShape(o->type,&shape,&pool);
    float f=DifficultyFactor(diff);

    switch(shape){
        case QSHAPE_TARGET_AMOUNT:
            strncpy(o->target,PoolPick(pool),MAX_GEN_NAME-1);
            o->amount = (pool==QPOOL_BOSS) ? 1 : (int)((3+rand()%8)*f);
            if(o->amount<1) o->amount=1;
            break;
        case QSHAPE_TARGET_ONLY:
            strncpy(o->target,PoolPick(pool),MAX_GEN_NAME-1);
            o->amount=1;
            break;
        case QSHAPE_TIME:
            if(pool!=QPOOL_NONE) strncpy(o->target,PoolPick(pool),MAX_GEN_NAME-1);
            o->amount=(int)((30+rand()%60)*f);
            break;
        case QSHAPE_CUSTOM:
            break;   // never reached — "Custom" is excluded from the random pick above
    }
    o->progress=0;
}

static void ObjectiveText(const Objective *o, char *out, int outSize)
{
    QuestShape shape; QuestPool pool;
    GetTypeShape(o->type,&shape,&pool);
    if(shape==QSHAPE_CUSTOM)             snprintf(out,outSize,"%s",o->customText);
    else if(pool==QPOOL_BOSS)            snprintf(out,outSize,"Defeat Boss \"%s\"",o->target);
    else if(shape==QSHAPE_TARGET_AMOUNT) snprintf(out,outSize,"%s %d %s",o->type,o->amount,o->target);
    else if(shape==QSHAPE_TARGET_ONLY)   snprintf(out,outSize,"%s %s",o->type,o->target);
    else if(strlen(o->target))           snprintf(out,outSize,"%s %s for %dh",o->type,o->target,o->amount);
    else                                 snprintf(out,outSize,"%s for %dh",o->type,o->amount);
}

static void GenerateRewards(Quest *q, Difficulty diff, int level)
{
    if(level<1) level=1;
    float f=DifficultyFactor(diff);
    q->xp=(int)((10+level*8)*f)+rand()%(level+10);
    q->cp=(int)((10+level*4)*f)+rand()%(level/2+10);
    q->rewardItemCount=0;   // item rewards removed for now
}

// Numbers the quest ("Quest N: <title>"). Used whenever no custom name was given.
static void AutoNameQuest(Quest *q)
{
    genQuestCounter++;
    const char *flavor = genTitles.count>0 ? genTitles.items[rand()%genTitles.count] : "Quest";
    snprintf(q->name,MAX_QNAME,"Quest %d: %s",genQuestCounter,flavor);
}

// Builds the summary desc from the objectives already set on the quest. Shared
// by the random generator and the manual quest builder, so both produce quests
// in the same format.
static void BuildQuestDesc(Quest *q)
{
    char desc[MAX_QDESC]={0};
    for(int i=0;i<q->objectiveCount;i++){
        char ot[96]; ObjectiveText(&q->objectives[i],ot,sizeof(ot));
        if(i>0) strncat(desc,"  +  ",MAX_QDESC-strlen(desc)-1);
        strncat(desc,ot,MAX_QDESC-strlen(desc)-1);
    }
    strncpy(q->desc,desc,MAX_QDESC-1);
}

// Picks the generated quest's level relative to the player's current level:
// under level 20 it's +/-5 either way; from level 25 on it's always at or
// below the player (-10..0), so quests stop being harder than you; the two
// ranges blend smoothly across the level 20-25 band instead of snapping.
static int PickQuestLevel(int playerLevel)
{
    int lo, hi;
    if(playerLevel<20){
        lo=playerLevel-5; hi=playerLevel+5;
    } else if(playerLevel>=25){
        lo=playerLevel-10; hi=playerLevel;
    } else {
        float t=(playerLevel-20)/5.f;   // 0 at level 20, 1 at level 25
        lo=(int)roundf((playerLevel-5)*(1.f-t) + (playerLevel-10)*t);
        hi=(int)roundf((playerLevel+5)*(1.f-t) + (playerLevel)*t);
    }
    if(lo<1) lo=1;
    if(hi<lo) hi=lo;
    if(hi>MAX_LEVEL-1) hi=MAX_LEVEL-1;
    if(lo>hi) lo=hi;
    return lo+rand()%(hi-lo+1);
}

static void GenerateQuest(int playerLevel)
{
    if(questCount>=MAX_QUESTS){ ShowNotif("Quest log is full."); return; }

    Quest *q=&quests[questCount++];
    memset(q,0,sizeof(Quest));
    q->type=0;          // "Quests" sub-tab only
    q->generated=true;
    q->level=PickQuestLevel(playerLevel);

    Difficulty diff=(Difficulty)(rand()%DIFFICULTY_COUNT);
    q->difficulty=diff;

    q->objectiveCount = 1+rand()%MAX_OBJECTIVES;
    for(int i=0;i<q->objectiveCount;i++) GenerateObjective(&q->objectives[i],diff);

    GenerateRewards(q,diff,q->level);
    AutoNameQuest(q);
    BuildQuestDesc(q);

    QuestSave();
    ShowNotif(TextFormat("Generated: %s (Lv %d, %s)",q->name,q->level,DifficultyName(diff)));
}

// ---- Manual Quest Builder — replaces the old free-text "+ New Quest" form.
//      Same underlying format as the random generator, just parameter-picked by hand. ----
static char       qb_name[MAX_QNAME]      = {0};
static int        qb_nameFocus  = 0;
static char       qb_desc[MAX_QDESC]      = {0};
static int        qb_descFocus  = 0;
static int        qb_level       = 1;
static Difficulty qb_diff        = DIFF_NORMAL;
static char       qb_cp[MAX_QNUM]      = {0};   // rewards are hand-set here, not auto-generated
static int        qb_cpFocus     = 0;
static char       qb_xp[MAX_QNUM]      = {0};
static int        qb_xpFocus     = 0;
static int        qb_typeIdx     = 0;
static int        qb_targetIdx   = 0;
static char       qb_target[MAX_GEN_NAME] = {0};
static char       qb_amount[MAX_QNUM]     = {0};
static int        qb_amountFocus = 0;
static char       qb_customText[128]      = {0};   // type=="Custom": the whole hand-written objective line
static int        qb_customTextFocus = 0;
static bool        qb_targetPickerOpen   = false;
static float        qb_targetPickerScroll = 0.f;
static char        qb_newTargetName[MAX_GEN_NAME] = {0};
static int         qb_newTargetFocus = 0;
static char        qb_targetSearch[MAX_GEN_NAME] = {0};
static int         qb_targetSearchFocus = 0;

// Only one of the builder's text fields should ever hold focus at once - otherwise
// GetCharPressed() (drained fully by the first TypeInto() call each frame) never
// reaches whichever field was clicked second, so it silently stops accepting input.
static void QBClearFocus(void)
{
    qb_nameFocus=0; qb_customTextFocus=0; qb_amountFocus=0;
    qb_descFocus=0; qb_targetSearchFocus=0; qb_newTargetFocus=0;
    qb_cpFocus=0; qb_xpFocus=0;
}

static bool ContainsCI(const char *hay, const char *needle)
{
    if(!*needle) return true;
    size_t hn=strlen(hay), nn=strlen(needle);
    if(nn>hn) return false;
    for(size_t i=0;i+nn<=hn;i++){
        size_t j=0;
        for(;j<nn;j++){
            char a=hay[i+j], b=needle[j];
            if(a>='A'&&a<='Z') a=(char)(a+32);
            if(b>='A'&&b<='Z') b=(char)(b+32);
            if(a!=b) break;
        }
        if(j==nn) return true;
    }
    return false;
}

static Objective  qb_staged[MAX_OBJECTIVES];
static int        qb_stagedCount = 0;

static const char *QBCurType(void)
{
    return genQuestTypeDefCount>0 ? genQuestTypeDefs[qb_typeIdx].name : "Kill";
}

// Picks target index 0 for the current type's pool (called when the type changes).
static void QBSetTargetForType(void)
{
    qb_targetIdx=0;
    QuestShape shape; QuestPool pool;
    GetTypeShape(QBCurType(),&shape,&pool);
    if(shape==QSHAPE_TIME && pool==QPOOL_NONE){ qb_target[0]='\0'; return; }
    NameList *l=PoolList(pool);
    if(!l || l->count==0){ strncpy(qb_target,"Something",MAX_GEN_NAME-1); return; }
    strncpy(qb_target,l->items[0],MAX_GEN_NAME-1);
}

// Steps through the current type's pool by hand instead of rerolling randomly.
static void QBCycleTarget(int dir)
{
    QuestShape shape; QuestPool pool;
    GetTypeShape(QBCurType(),&shape,&pool);
    if(shape==QSHAPE_TIME && pool==QPOOL_NONE) return;
    NameList *l=PoolList(pool);
    if(!l || l->count==0) return;
    qb_targetIdx=(qb_targetIdx+dir+l->count)%l->count;
    strncpy(qb_target,l->items[qb_targetIdx],MAX_GEN_NAME-1);
}

// Picks a target directly by its index in the current type's pool (used by the
// tabular target-picker list, so the user selects instead of only cycling).
static void QBSelectTarget(int idx)
{
    QuestShape shape; QuestPool pool;
    GetTypeShape(QBCurType(),&shape,&pool);
    NameList *l=PoolList(pool);
    if(!l || idx<0 || idx>=l->count) return;
    qb_targetIdx=idx;
    strncpy(qb_target,l->items[idx],MAX_GEN_NAME-1);
}

static void QBSuggestAmount(void)
{
    QuestShape shape; QuestPool pool;
    GetTypeShape(QBCurType(),&shape,&pool);
    float f=DifficultyFactor(qb_diff);
    int amt;
    if(shape==QSHAPE_CUSTOM)             amt=1;   // just a one-shot complete/incomplete flag
    else if(shape==QSHAPE_TARGET_AMOUNT) amt = (pool==QPOOL_BOSS) ? 1 : (int)((3+rand()%8)*f);
    else if(shape==QSHAPE_TARGET_ONLY)   amt=1;
    else                                 amt=(int)((30+rand()%60)*f);
    if(shape!=QSHAPE_CUSTOM && amt<1) amt=1;
    snprintf(qb_amount,MAX_QNUM,"%d",amt);
}

// Prefills CP/XP with the same formula the random generator uses, as a starting
// point - the fields stay freely editable before the quest is actually created.
static void QBSuggestRewards(void)
{
    int lvl=qb_level<1?1:qb_level;
    float f=DifficultyFactor(qb_diff);
    int xp=(int)((10+lvl*8)*f)+rand()%(lvl+10);
    int cp=(int)((10+lvl*4)*f)+rand()%(lvl/2+10);
    snprintf(qb_xp,MAX_QNUM,"%d",xp);
    snprintf(qb_cp,MAX_QNUM,"%d",cp);
}

void ResetQuestBuilder(int playerLevel)
{
    memset(qb_name,0,MAX_QNAME);
    qb_nameFocus=0;
    memset(qb_desc,0,MAX_QDESC);
    qb_descFocus=0;
    qb_level=playerLevel<1?1:(playerLevel>MAX_LEVEL-1?MAX_LEVEL-1:playerLevel);
    qb_diff=DIFF_NORMAL;
    qb_typeIdx=0;
    qb_stagedCount=0;
    qb_targetPickerOpen=false;
    qb_targetPickerScroll=0.f;
    memset(qb_newTargetName,0,MAX_GEN_NAME);
    qb_newTargetFocus=0;
    memset(qb_targetSearch,0,MAX_GEN_NAME);
    qb_targetSearchFocus=0;
    memset(qb_customText,0,sizeof(qb_customText));
    qb_customTextFocus=0;
    QBSetTargetForType();
    QBSuggestAmount();
    qb_amountFocus=0;
    QBSuggestRewards();
    qb_cpFocus=0; qb_xpFocus=0;
}

void DrawQuestBuilderModal(float dt)
{
    // Snapshot at frame start: the click that OPENS the target picker below must
    // not also be seen by the picker's own hover/click checks this same frame
    // (same mouse position + IsMouseButtonPressed still true), or it would pick
    // whatever's under the cursor and instantly close again.
    bool targetPickerWasOpen = qb_targetPickerOpen;

    DrawRectangle(0,0,900,600,Q_OVERLAY);

    int mw=600, mh=596;
    int mx=(900-mw)/2, my=(600-mh)/2;
    Rectangle modal={(float)mx,(float)my,(float)mw,(float)mh};
    DrawRectangleRounded(modal,0.06f,8,(Color){15,11,32,255});
    DrawRectangleRoundedLinesEx(modal,0.06f,8,1.5f,Q_BORDER2);

    DrawRectangleRounded((Rectangle){(float)mx,(float)my,(float)mw,46},0.06f,6,(Color){25,18,50,255});
    DrawText("New Quest - Build From Parameters",mx+18,my+13,17,Q_GPALE);

    Rectangle closeR={(float)(mx+mw-40),(float)(my+8),30,30};
    bool cHov=CheckCollisionPointRec(GetMousePosition(),closeR);
    DrawRectangleRounded(closeR,0.3f,4,cHov?(Color){60,20,20,255}:C_PANEL2);
    DrawXMark(closeR,2.f,C_RED);
    if(cHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) quest_create=false;

    int fy=my+58;

    // Name (optional — auto-generated if left blank)
    Rectangle nameR={(float)(mx+18),(float)(fy+18),(float)(mw-36),32};
    if(CheckCollisionPointRec(GetMousePosition(),nameR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ QBClearFocus(); qb_nameFocus=1; }
    QInputBox(nameR,"Quest Name (optional - auto-generated if left blank)",qb_name,qb_nameFocus==1);
    if(qb_nameFocus==1) TypeInto(qb_name,MAX_QNAME,false,dt);
    fy+=64;

    // Level — recommended character level; drives the XP reward
    DrawText("LEVEL",mx+18,fy,12,Q_TDIM); fy+=16;
    {
        Rectangle lvlMinus={(float)(mx+18),(float)fy,32.f,28.f};
        Rectangle lvlPlus ={(float)(mx+18+38),(float)fy,32.f,28.f};
        bool lvlShift=(IsKeyDown(KEY_LEFT_SHIFT)||IsKeyDown(KEY_RIGHT_SHIFT));
        int lvlStep=lvlShift?10:1;
        if(SmallBtn(lvlMinus,"-",C_PANEL3,C_TEXT)){ qb_level-=lvlStep; if(qb_level<1) qb_level=1; QBSuggestRewards(); }
        if(SmallBtn(lvlPlus,"+",C_PANEL3,C_TEXT)){ qb_level+=lvlStep; if(qb_level>MAX_LEVEL-1) qb_level=MAX_LEVEL-1; QBSuggestRewards(); }
        char lvlTxt[16]; snprintf(lvlTxt,sizeof(lvlTxt),"Lv %d",qb_level);
        DrawText(lvlTxt,mx+18+80,fy+6,16,C_GOLD_L);
    }
    fy+=40;

    // Difficulty
    DrawText("DIFFICULTY",mx+18,fy,12,Q_TDIM); fy+=16;
    int pillW=(mw-36-4*8)/5;
    for(int d=0;d<DIFFICULTY_COUNT;d++){
        Rectangle pr={(float)(mx+18+d*(pillW+8)),(float)fy,(float)pillW,28.f};
        bool active=(qb_diff==d);
        bool hov=CheckCollisionPointRec(GetMousePosition(),pr);
        Color dc=DifficultyColor((Difficulty)d);
        DrawRectangleRounded(pr,0.25f,8,active?(Color){dc.r,dc.g,dc.b,60}:(hov?C_PANEL3:C_PANEL2));
        DrawRectangleRoundedLinesEx(pr,0.25f,8,1.f,active?dc:C_BORDER);
        const char *dn=DifficultyName((Difficulty)d);
        DrawText(dn,(int)(pr.x+(pr.width-MeasureText(dn,12))/2),(int)(pr.y+8),12,active?dc:C_MUTED);
        if(hov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ qb_diff=(Difficulty)d; QBSuggestAmount(); QBSuggestRewards(); }
    }
    fy+=42;

    // Rewards — prefilled from Level/Difficulty, freely editable
    // (QInputBox draws its own "CP Reward"/"XP Reward" label above each box.)
    {
        int halfW2=(mw-36-12)/2;
        Rectangle cpR={(float)(mx+18),(float)(fy+18),(float)halfW2,30.f};
        Rectangle xpR={(float)(mx+18+halfW2+12),(float)(fy+18),(float)halfW2,30.f};
        if(CheckCollisionPointRec(GetMousePosition(),cpR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ QBClearFocus(); qb_cpFocus=1; }
        if(CheckCollisionPointRec(GetMousePosition(),xpR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ QBClearFocus(); qb_xpFocus=1; }
        QInputBox(cpR,"CP Reward",qb_cp,qb_cpFocus==1);
        QInputBox(xpR,"XP Reward",qb_xp,qb_xpFocus==1);
        if(qb_cpFocus==1) TypeInto(qb_cp,MAX_QNUM,true,dt);
        if(qb_xpFocus==1) TypeInto(qb_xp,MAX_QNUM,true,dt);
    }
    fy+=58;

    // Objective type cycler
    DrawText("OBJECTIVE",mx+18,fy,12,Q_TDIM); fy+=16;
    Rectangle typeMinus={(float)(mx+18),(float)fy,28.f,30.f};
    Rectangle typePlus ={(float)(mx+18+(mw-36)-28.f),(float)fy,28.f,30.f};
    const char *curType = QBCurType();
    int tw=MeasureText(curType,16);
    DrawText(curType,(int)(mx+18+(mw-36)/2-tw/2),(int)(fy+7),16,C_GOLD_L);
    if(SmallBtn(typeMinus,"<",C_PANEL3,C_TEXT)&&genQuestTypeDefCount>0){
        qb_typeIdx=(qb_typeIdx-1+genQuestTypeDefCount)%genQuestTypeDefCount;
        QBSetTargetForType(); QBSuggestAmount(); memset(qb_customText,0,sizeof(qb_customText));
    }
    if(SmallBtn(typePlus,">",C_PANEL3,C_TEXT)&&genQuestTypeDefCount>0){
        qb_typeIdx=(qb_typeIdx+1)%genQuestTypeDefCount;
        QBSetTargetForType(); QBSuggestAmount(); memset(qb_customText,0,sizeof(qb_customText));
    }
    fy+=46;

    QuestShape shape; QuestPool pool;
    GetTypeShape(curType,&shape,&pool);
    bool hasTarget = !(shape==QSHAPE_TIME && pool==QPOOL_NONE);
    int halfW=(mw-36-12)/2;

    if(shape==QSHAPE_CUSTOM){
        // Fully hand-written objective: no target picker, no amount, no presets, nothing saved to a list.
        DrawText("Custom Objective Text",mx+18,fy,12,Q_TDIM);
        fy+=16;
        Rectangle customR={(float)(mx+18),(float)fy,(float)(mw-36),30.f};
        if(!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),customR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ QBClearFocus(); qb_customTextFocus=1; }
        DrawRectangleRec(customR,Q_INBG);
        DrawRectangleLinesEx(customR,1.5f,qb_customTextFocus==1?C_GOLD:C_BORDER);
        DrawText(strlen(qb_customText)?qb_customText:"Write exactly what this objective should say...",
                 (int)customR.x+8,(int)customR.y+8,14,strlen(qb_customText)?C_TEXT:C_MUTED);
        if(qb_customTextFocus==1) TypeInto(qb_customText,(int)sizeof(qb_customText),false,dt);
        fy+=42;
    } else if(pool==QPOOL_BOSS){
        // Boss objectives are always exactly one boss - no amount to configure.
        DrawText("Target",mx+18,fy,12,Q_TDIM);
        fy+=16;
        DrawText(strlen(qb_target)?qb_target:"-",mx+18,fy+7,14,C_TEXT);
        Rectangle chooseR={(float)(mx+18+(mw-36)-90),(float)fy,90.f,28.f};
        bool chooseHov=!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),chooseR);
        QButton(chooseR,"Choose...",C_PANEL3,C_MUTED,chooseHov);
        if(chooseHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
            qb_targetPickerOpen=!qb_targetPickerOpen;
            qb_targetPickerScroll=0.f;
            memset(qb_targetSearch,0,MAX_GEN_NAME);
            QBClearFocus();
        }
        fy+=42;
    } else {
        DrawText("Target",mx+18,fy,12,Q_TDIM);
        DrawText(shape==QSHAPE_TIME?"Hours":"Amount",mx+18+halfW+12,fy,12,Q_TDIM);
        fy+=16;

        if(hasTarget){
            DrawText(strlen(qb_target)?qb_target:"-",mx+18,fy+7,14,C_TEXT);
            Rectangle chooseR={(float)(mx+18+halfW-90),(float)fy,90.f,28.f};
            bool chooseHov=!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),chooseR);
            QButton(chooseR,"Choose...",C_PANEL3,C_MUTED,chooseHov);
            if(chooseHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
                qb_targetPickerOpen=!qb_targetPickerOpen;
                qb_targetPickerScroll=0.f;
                memset(qb_targetSearch,0,MAX_GEN_NAME);
                QBClearFocus();
            }
        } else {
            DrawText("(none)",mx+18,fy+7,14,C_MUTED);
            qb_targetPickerOpen=false;
        }

        Rectangle amtR={(float)(mx+18+halfW+12),(float)fy,(float)halfW,28.f};
        if(!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),amtR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ QBClearFocus(); qb_amountFocus=1; }
        DrawRectangleRec(amtR,Q_INBG);
        DrawRectangleLinesEx(amtR,1.5f,qb_amountFocus==1?C_GOLD:C_BORDER);
        DrawText(qb_amount,(int)amtR.x+8,(int)amtR.y+7,14,C_TEXT);
        if(qb_amountFocus==1) TypeInto(qb_amount,MAX_QNUM,true,dt);
        fy+=42;
    }

    Rectangle addR={(float)(mx+18),(float)fy,(float)(mw-36),30.f};
    bool addHov=!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),addR);
    QButton(addR,"+ Add Objective",(Color){20,45,20,255},C_GREEN_L,addHov);
    if(addHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
        if(qb_stagedCount>=MAX_OBJECTIVES) ShowNotif("Max 4 objectives.");
        else if(shape==QSHAPE_CUSTOM && !strlen(qb_customText)) ShowNotif("Write the objective text first.");
        else {
            Objective *o=&qb_staged[qb_stagedCount++];
            memset(o,0,sizeof(*o));
            strncpy(o->type,curType,MAX_GEN_NAME-1);
            if(shape==QSHAPE_CUSTOM){
                strncpy(o->customText,qb_customText,sizeof(o->customText)-1);
                o->amount=1;   // one-shot complete/incomplete flag, not a real count
                memset(qb_customText,0,sizeof(qb_customText));
            } else {
                strncpy(o->target,qb_target,MAX_GEN_NAME-1);
                o->amount = (pool==QPOOL_BOSS) ? 1 : atoi(qb_amount);
                if(o->amount<1) o->amount=1;
                QBCycleTarget(1); QBSuggestAmount();
            }
            o->progress=0;
        }
    }
    fy+=38;

    DrawText(TextFormat("Objectives: %d / %d",qb_stagedCount,MAX_OBJECTIVES),mx+18,fy,12,qb_stagedCount>0?C_GREEN_L:C_MUTED);
    fy+=18;
    for(int i=0;i<qb_stagedCount;i++){
        char ot[96]; ObjectiveText(&qb_staged[i],ot,sizeof(ot));
        Rectangle rr={(float)(mx+18),(float)fy,(float)(mw-36),26};
        DrawRectangleRounded(rr,0.12f,6,i%2?C_PANEL:C_PANEL2);
        DrawText(ot,(int)rr.x+8,(int)rr.y+5,13,C_TEXT);
        Rectangle rmR={(float)(rr.x+rr.width-60),(float)rr.y+2,54,22};
        bool rmHov=!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),rmR);
        QButton(rmR,"Remove",(Color){40,10,10,255},C_RED,rmHov);
        if(rmHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
            for(int k=i;k<qb_stagedCount-1;k++) qb_staged[k]=qb_staged[k+1];
            qb_stagedCount--;
        }
        fy+=28;
    }
    fy+=6;

    // Description (optional — left blank stays blank, it's not built from the objectives)
    Rectangle descR={(float)(mx+18),(float)(fy+18),(float)(mw-36),36};
    if(!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),descR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ QBClearFocus(); qb_descFocus=1; }
    QInputBox(descR,"Description (optional)",qb_desc,qb_descFocus==1);
    if(qb_descFocus==1) TypeInto(qb_desc,MAX_QDESC,false,dt);
    fy+=6;

    Rectangle okR={(float)(mx+18),(float)(my+mh-46),(float)(mw-36)/2-6,38};
    Rectangle cnR={(float)(mx+18+(mw-36)/2+6),(float)(my+mh-46),(float)(mw-36)/2-6,38};
    bool okH=!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),okR);
    bool cnH=!targetPickerWasOpen&&CheckCollisionPointRec(GetMousePosition(),cnR);
    QButton(okR,"Create Quest",(Color){20,45,20,255},C_GREEN,okH);
    QButton(cnR,"Cancel",(Color){40,10,10,255},C_RED,cnH);
    if(cnH&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) quest_create=false;

    if(okH&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
        if(qb_stagedCount==0)                ShowNotif("Add at least one objective.");
        else if(questCount>=MAX_QUESTS)      ShowNotif("Quest log is full.");
        else {
            Quest *q=&quests[questCount++];
            memset(q,0,sizeof(Quest));
            q->type=0; q->generated=true; q->difficulty=qb_diff; q->level=qb_level;
            q->objectiveCount=qb_stagedCount;
            for(int i=0;i<qb_stagedCount;i++) q->objectives[i]=qb_staged[i];
            q->cp=atoi(qb_cp); q->xp=atoi(qb_xp);
            q->rewardItemCount=0;   // item rewards removed for now
            if(strlen(qb_name)) strncpy(q->name,qb_name,MAX_QNAME-1);
            else                AutoNameQuest(q);
            strncpy(q->desc,qb_desc,MAX_QDESC-1);   // left blank on purpose stays blank
            QuestSave();
            quest_create=false;
            ShowNotif(TextFormat("Quest created: %s",q->name));
        }
    }

    // Tabular target picker — a big, centered, multi-column browser over the
    // current type's pool (with search + a row to add a brand-new entry).
    if(targetPickerWasOpen && hasTarget){
        NameList *l=PoolList(pool);
        int n = l?l->count:0;

        DrawRectangle(0,0,900,600,(Color){0,0,0,150});

        int popW=680, popH=460;
        int popX=(900-popW)/2, popY=(600-popH)/2;
        Rectangle popRect={(float)popX,(float)popY,(float)popW,(float)popH};
        DrawRectangleRounded(popRect,0.05f,8,(Color){14,11,30,255});
        DrawRectangleRoundedLinesEx(popRect,0.05f,8,1.5f,C_GOLD);

        DrawRectangleRounded((Rectangle){(float)popX,(float)popY,(float)popW,42},0.05f,6,(Color){22,17,48,255});
        DrawText("Choose Target",popX+16,popY+11,17,Q_GPALE);
        Rectangle popCloseR={(float)(popX+popW-38),(float)(popY+6),30,30};
        bool popCloseHov=CheckCollisionPointRec(GetMousePosition(),popCloseR);
        DrawRectangleRounded(popCloseR,0.3f,4,popCloseHov?(Color){60,20,20,255}:C_PANEL2);
        DrawXMark(popCloseR,2.f,C_RED);
        if(popCloseHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) qb_targetPickerOpen=false;

        int py2=popY+54;

        Rectangle searchR={(float)(popX+16),(float)py2,(float)(popW-32),30.f};
        if(CheckCollisionPointRec(GetMousePosition(),searchR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ QBClearFocus(); qb_targetSearchFocus=1; }
        DrawRectangleRec(searchR,Q_INBG);
        DrawRectangleLinesEx(searchR,1.5f,qb_targetSearchFocus==1?C_GOLD:C_BORDER);
        DrawText(strlen(qb_targetSearch)?qb_targetSearch:"Search...",(int)searchR.x+8,(int)searchR.y+7,14,strlen(qb_targetSearch)?C_TEXT:C_MUTED);
        if(qb_targetSearchFocus==1) TypeInto(qb_targetSearch,MAX_GEN_NAME,false,dt);
        py2+=42;

        Rectangle newR={(float)(popX+16),(float)py2,(float)(popW-32-84),30.f};
        Rectangle addNewR={(float)(popX+popW-16-76),(float)py2,76.f,30.f};
        if(CheckCollisionPointRec(GetMousePosition(),newR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ QBClearFocus(); qb_newTargetFocus=1; }
        DrawRectangleRec(newR,Q_INBG);
        DrawRectangleLinesEx(newR,1.5f,qb_newTargetFocus==1?C_GOLD:C_BORDER);
        DrawText(strlen(qb_newTargetName)?qb_newTargetName:"New entry...",(int)newR.x+8,(int)newR.y+7,14,strlen(qb_newTargetName)?C_TEXT:C_MUTED);
        if(qb_newTargetFocus==1) TypeInto(qb_newTargetName,MAX_GEN_NAME,false,dt);
        bool addNewHov=CheckCollisionPointRec(GetMousePosition(),addNewR);
        QButton(addNewR,"Add",(Color){20,45,20,255},C_GREEN_L,addNewHov);
        if(addNewHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
            if(!strlen(qb_newTargetName)) ShowNotif("Enter a name first.");
            else if(!PoolAppend(pool,qb_newTargetName)) ShowNotif("Could not add that entry.");
            else {
                int idx=PoolFindIndex(pool,qb_newTargetName);
                if(idx>=0) QBSelectTarget(idx);
                memset(qb_newTargetName,0,MAX_GEN_NAME);
                qb_newTargetFocus=0;
                ShowNotif("Added to the list.");
            }
        }
        py2+=40;
        DrawLine(popX+16,py2,popX+popW-16,py2,C_BORDER);
        py2+=20;

        int filtered[MAX_GEN_ENTRIES], fCount=0;
        for(int i=0;i<n;i++) if(ContainsCI(l->items[i],qb_targetSearch)) filtered[fCount++]=i;

        int listY=py2, listH=(popY+popH-12)-listY;
        if(fCount==0) DrawText(n==0?"No entries yet - add one above.":"No matches.",popX+16,listY+10,14,C_MUTED);

        int cols=3, colW=(popW-32)/cols, rowH=28;
        int rows=(fCount+cols-1)/cols;

        if(CheckCollisionPointRec(GetMousePosition(),(Rectangle){(float)popX,(float)listY,(float)popW,(float)listH}))
            qb_targetPickerScroll -= GetMouseWheelMove()*rowH;
        float maxS=fmaxf(0.f,(float)(rows*rowH-listH));
        if(qb_targetPickerScroll<0.f) qb_targetPickerScroll=0.f;
        if(qb_targetPickerScroll>maxS) qb_targetPickerScroll=maxS;

        // A few px of headroom above listY so the first row's top border isn't clipped by the scissor edge.
        BeginScissorMode(popX,listY-3,popW,listH+3);
        for(int fi=0;fi<fCount;fi++){
            int i=filtered[fi];
            int col=fi%cols, row=fi/cols;
            int cx3=popX+16+col*colW;
            int cy3=listY+row*rowH-(int)qb_targetPickerScroll;
            if(cy3+rowH<listY||cy3>listY+listH) continue;

            Rectangle rr={(float)cx3,(float)cy3,(float)(colW-8),(float)(rowH-6)};
            bool hov=CheckCollisionPointRec(GetMousePosition(),rr);
            bool sel=(i==qb_targetIdx);
            DrawRectangleRounded(rr,0.15f,6, sel?(Color){60,45,10,255}:(hov?C_PANEL3:C_PANEL2));
            DrawRectangleRoundedLinesEx(rr,0.15f,6,1.f, sel?C_GOLD:(hov?C_BORDER:C_PANEL2));
            char label[40]; strncpy(label,l->items[i],39); label[39]='\0';
            while(MeasureText(label,13)>(int)rr.width-12&&strlen(label)>0) label[strlen(label)-1]='\0';
            DrawText(label,(int)rr.x+8,(int)rr.y+6,13,sel?C_GOLD_L:C_TEXT);
            if(hov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
                QBSelectTarget(i);
                qb_targetPickerOpen=false;
            }
        }
        EndScissorMode();
    }
}

// Quest list renderer
static void DrawQuestList(int cx, int cy, int cw, int listH, int type)
{
    int idxs[MAX_QUESTS], n=0;
    for(int i=0;i<questCount;i++) if(quests[i].type==type) idxs[n++]=i;

    int totalH=n*QUEST_ROW_H;
    int maxScroll=totalH-listH;
    if(maxScroll<0) maxScroll=0;

    Vector2 mp=GetMousePosition();
    if(mp.x>cx&&mp.x<cx+cw&&mp.y>cy&&mp.y<cy+listH&&!quest_create&&quest_view<0){
        float wh=GetMouseWheelMove();
        if(wh!=0){
            quest_scroll-=(int)(wh*22);
            if(quest_scroll<0) quest_scroll=0;
            if(quest_scroll>maxScroll) quest_scroll=maxScroll;
        }
    }

    BeginScissorMode(cx,cy,cw,listH);

    if(n==0) DrawText("No quests yet — create one below.",cx+16,cy+20,14,Q_TDARK);

    for(int ii=0;ii<n;ii++){
        int qi=idxs[ii];
        Quest *q=&quests[qi];
        int ry=cy+ii*QUEST_ROW_H-quest_scroll;
        if(ry+QUEST_ROW_H<cy||ry>cy+listH) continue;

        Rectangle row={(float)cx,(float)ry,(float)cw,QUEST_ROW_H-4};
        bool hov=CheckCollisionPointRec(mp,row)&&!quest_create&&quest_view<0;
        bool done=q->done;

        DrawRectangleRounded(row,0.05f,4,done?Q_DONEBG:(hov?Q_HOVER:C_PANEL));
        DrawRectangleRoundedLinesEx(row,0.05f,4,1.f,done?(Color){20,80,40,255}:(hov?Q_BORDER2:C_BORDER));
        DrawRectangle(cx,ry,4,QUEST_ROW_H-4,done?C_GREEN:C_RED);

        int cbY=ry+(QUEST_ROW_H-4-20)/2;
        Rectangle cb={(float)(cx+12),(float)cbY,20,20};
        DrawRectangleRec(cb,Q_INBG);
        DrawRectangleLinesEx(cb,1.5f,done?C_GREEN:Q_BORDER2);
        if(done) DrawXMark(cb,2.f,C_GREEN);

        if(hov&&CheckCollisionPointRec(mp,cb)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
            q->done=!q->done; QuestSave();
            ShowNotif(q->done?"Quest completed!":"Quest reopened.");
        }

        DrawText(q->name,cx+40,ry+8,15,done?Q_TDIM:Q_GPALE);
        if(q->generated){
            int nw=MeasureText(q->name,15);
            Color dc=DifficultyColor(q->difficulty);
            int bx2=cx+40+nw+10;
            char lvlLbl[16]; snprintf(lvlLbl,sizeof(lvlLbl),"Lv %d",q->level);
            Badge(bx2,ry+6,lvlLbl,C_PANEL3,C_GOLD_L);
            bx2+=MeasureText(lvlLbl,12)+16+6;
            Badge(bx2,ry+6,DifficultyName(q->difficulty),(Color){dc.r,dc.g,dc.b,40},dc);
        }
        char prev[52]; strncpy(prev,q->desc,51); prev[51]='\0';
        if(strlen(prev)>44){ prev[44]='.'; prev[45]='.'; prev[46]='.'; prev[47]='\0'; }
        DrawText(prev,cx+40,ry+28,12,Q_TDIM);

        int bx=cx+cw-184;
        QBadge(bx,   ry+8,"CP",q->cp,Q_CP_DIM,Q_CP);
        QBadge(bx+90,ry+8,"XP",q->xp,Q_XP_DIM,Q_XP);

        Rectangle vb={(float)(cx+cw-184),(float)(ry+30),88,22};
        Rectangle db={(float)(cx+cw- 90),(float)(ry+30),86,22};
        bool vhov=hov&&CheckCollisionPointRec(mp,vb);
        bool dhov=hov&&CheckCollisionPointRec(mp,db);
        QButton(vb,"View",  C_PANEL2,            Q_BORDER2,vhov);
        QButton(db,"Delete",(Color){40,10,10,255},C_RED,    dhov);

        if(vhov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) quest_view=qi;

        if(dhov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
            for(int k=qi;k<questCount-1;k++) quests[k]=quests[k+1];
            questCount--; QuestSave();
            ShowNotif("Quest deleted.");
            if(quest_scroll>QUEST_ROW_H) quest_scroll-=QUEST_ROW_H;
        }
    }
    EndScissorMode();

    if(maxScroll>0&&n>0){
        float ratio=(float)listH/(n*QUEST_ROW_H);
        float barH=listH*ratio; if(barH<24)barH=24;
        float barY=(float)cy+(float)quest_scroll/(n*QUEST_ROW_H)*listH;
        DrawRectangleRounded((Rectangle){(float)(cx+cw-6),(float)barY,5,barH},0.5f,4,Q_BORDER2);
    }
}

// Quest create modal with scale animation
void DrawQuestCreateModal(int quest_type, float dt)
{
    q_mscale+=dt*6.f;
    if(q_mscale>1.f) q_mscale=1.f;
    float s=q_mscale; s=s*s*(3-2*s);

    DrawRectangle(0,0,900,600,Q_OVERLAY);

    int mw=520, mh=460;
    int rx=(int)(450-mw*s/2), ry=(int)(300-mh*s/2);
    int rw=(int)(mw*s), rh=(int)(mh*s);
    if(rw<10||rh<10) return;

    int mx=(900-mw)/2, my=(600-mh)/2;

    Rectangle modal={(float)rx,(float)ry,(float)rw,(float)rh};
    DrawRectangleRounded(modal,0.07f,8,(Color){15,11,32,255});
    DrawRectangleRoundedLinesEx(modal,0.07f,8,1.5f,Q_BORDER2);
    DrawRectangleRoundedLinesEx(
        (Rectangle){modal.x-2,modal.y-2,modal.width+4,modal.height+4},
        0.07f,8,1.f,(Color){C_GOLD.r,C_GOLD.g,C_GOLD.b,50});

    if(s<0.8f) return;

    DrawRectangleRounded((Rectangle){(float)mx,(float)my,(float)mw,46},0.06f,6,(Color){25,18,50,255});
    const char *ttl=quest_type==1?"New Story-Quest":"New Quest";
    DrawText(ttl,mx+18,my+13,18,Q_GPALE);

    Rectangle closeR={(float)(mx+mw-40),(float)(my+8),30,30};
    bool cHov=CheckCollisionPointRec(GetMousePosition(),closeR);
    DrawRectangleRounded(closeR,0.3f,4,cHov?(Color){60,20,20,255}:C_PANEL2);
    DrawXMark(closeR,2.f,C_RED);
    if(cHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) quest_create=false;

    int fy=my+60;

    Rectangle nameR={(float)(mx+18),(float)(fy+18),(float)(mw-36),34};
    if(CheckCollisionPointRec(GetMousePosition(),nameR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) q_focus=0;
    QInputBox(nameR,"Quest Name",q_name,q_focus==0);
    if(q_focus==0) TypeInto(q_name,MAX_QNAME,false,dt);
    fy+=66;

    Rectangle descR={(float)(mx+18),(float)(fy+18),(float)(mw-36),58};
    if(CheckCollisionPointRec(GetMousePosition(),descR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) q_focus=1;
    QInputBox(descR,"Description",q_desc,q_focus==1);
    if(q_focus==1) TypeInto(q_desc,MAX_QDESC,false,dt);
    fy+=96;

    int hw2=(mw-36)/2-6;
    Rectangle cpR={(float)(mx+18),(float)(fy+18),(float)hw2,34};
    Rectangle xpR={(float)(mx+18+hw2+12),(float)(fy+18),(float)hw2,34};
    if(CheckCollisionPointRec(GetMousePosition(),cpR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) q_focus=2;
    if(CheckCollisionPointRec(GetMousePosition(),xpR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) q_focus=3;
    QInputBox(cpR,"CP Reward",q_cp,q_focus==2);
    QInputBox(xpR,"XP Reward",q_xp,q_focus==3);
    if(q_focus==2) TypeInto(q_cp,MAX_QNUM,true,dt);
    if(q_focus==3) TypeInto(q_xp,MAX_QNUM,true,dt);
    if(IsKeyPressed(KEY_TAB)) q_focus=(q_focus+1)%4;

    Rectangle okR={(float)(mx+18),(float)(my+mh-54),(float)(mw-36)/2-6,40};
    Rectangle cnR={(float)(mx+18+(mw-36)/2+6),(float)(my+mh-54),(float)(mw-36)/2-6,40};
    bool okH=CheckCollisionPointRec(GetMousePosition(),okR);
    bool cnH=CheckCollisionPointRec(GetMousePosition(),cnR);
    QButton(okR,"Create Quest",(Color){20,45,20,255},C_GREEN,okH);
    QButton(cnR,"Cancel",     (Color){40,10,10,255},C_RED,  cnH);
    if(cnH&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) quest_create=false;

    bool doCreate=(okH&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON))||(IsKeyPressed(KEY_ENTER)&&q_focus!=1);
    if(doCreate){
        if(!strlen(q_name)){ ShowNotif("Please enter a quest name!"); }
        else if(questCount<MAX_QUESTS){
            Quest *nq=&quests[questCount++];
            memset(nq,0,sizeof(Quest));
            strncpy(nq->name,q_name,MAX_QNAME-1);
            strncpy(nq->desc,q_desc,MAX_QDESC-1);
            nq->cp=atoi(q_cp); nq->xp=atoi(q_xp);
            nq->done=false; nq->type=quest_type;
            QuestSave();
            quest_create=false;
            ShowNotif("Quest created!");
        }
    }
}

// Quest view modal
void DrawQuestViewModal(void)
{
    if(quest_view<0||quest_view>=questCount) return;
    Quest *q=&quests[quest_view];

    DrawRectangle(0,0,900,600,Q_OVERLAY);

    int mw = q->generated?520:500;
    int mh = q->generated?480:380;
    int mx=(900-mw)/2, my=(600-mh)/2;
    Rectangle modal={(float)mx,(float)my,(float)mw,(float)mh};
    DrawRectangleRounded(modal,0.07f,8,(Color){14,11,30,255});
    DrawRectangleRoundedLinesEx(modal,0.07f,8,1.5f,Q_GDIM);
    DrawRectangleRoundedLinesEx(
        (Rectangle){modal.x-2,modal.y-2,modal.width+4,modal.height+4},
        0.07f,8,1.f,(Color){C_GOLD.r,C_GOLD.g,C_GOLD.b,35});

    DrawRectangleRounded((Rectangle){(float)mx,(float)my,(float)mw,48},0.07f,6,(Color){22,17,48,255});
    DrawText(q->name,mx+18,my+14,17,q->done?Q_TDIM:Q_GPALE);
    Color sc=q->done?C_GREEN:C_RED;
    const char *sl=q->done?"Completed":"Active";
    DrawText(sl,mx+mw-MeasureText(sl,13)-16,my+17,13,sc);

    QBadge(mx+18, my+56,"CP",q->cp,Q_CP_DIM,Q_CP);
    QBadge(mx+120,my+56,"XP",q->xp,Q_XP_DIM,Q_XP);
    if(q->generated){
        char lvlLbl[16]; snprintf(lvlLbl,sizeof(lvlLbl),"Lv %d",q->level);
        Badge(mx+222,my+56,lvlLbl,C_PANEL3,C_GOLD_L);
        Color dc=DifficultyColor(q->difficulty);
        Badge(mx+222+MeasureText(lvlLbl,12)+16+6,my+56,DifficultyName(q->difficulty),(Color){dc.r,dc.g,dc.b,40},dc);
    }

    DrawLine(mx+18,my+86,mx+mw-18,my+86,C_BORDER);

    if(!q->generated){
        DrawText("Description:",mx+18,my+96,12,Q_TDIM);
        if(!strlen(q->desc)) DrawText("No description.",mx+18,my+114,14,Q_TDARK);
        else QTextWrap(q->desc,mx+18,my+114,14,mw-44,C_TEXT);
    } else {
        int py=my+96;
        DrawText("OBJECTIVES",mx+18,py,12,Q_TDIM); py+=18;
        for(int i=0;i<q->objectiveCount;i++){
            Objective *o=&q->objectives[i];
            bool complete=(o->progress>=o->amount);
            char ot[96]; ObjectiveText(o,ot,sizeof(ot));

            Rectangle rr={(float)(mx+18),(float)py,(float)(mw-36),28};
            DrawRectangleRounded(rr,0.12f,6,complete?(Color){16,38,20,255}:C_PANEL2);
            DrawText(ot,(int)rr.x+10,(int)rr.y+7,13,complete?C_GREEN_L:C_TEXT);

            char pr[24]; snprintf(pr,sizeof(pr),"%d / %d",o->progress,o->amount);
            DrawText(pr,(int)(rr.x+rr.width-150),(int)rr.y+7,13,complete?C_GREEN_L:C_MUTED);

            Rectangle doneR={rr.x+rr.width-84,rr.y+3,76,22};
            if(!complete){
                bool doneHov=CheckCollisionPointRec(GetMousePosition(),doneR);
                QButton(doneR,"Complete",(Color){20,45,20,255},C_GREEN_L,doneHov);
                if(doneHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
                    o->progress=o->amount;
                    QuestSave();
                }
            } else {
                DrawText("Done",(int)doneR.x+22,(int)doneR.y+4,13,C_GREEN_L);
            }
            py+=32;
        }

        if(q->rewardItemCount>0){
            py+=6;
            DrawText("ITEM REWARDS",mx+18,py,12,Q_TDIM); py+=16;
            int cx2=mx+18;
            for(int i=0;i<q->rewardItemCount;i++){
                int w=MeasureText(q->rewardItems[i],12)+16;
                Badge(cx2,py,q->rewardItems[i],Q_XP_DIM,Q_XP);
                cx2+=w+8;
            }
        }
    }

    int bW=(mw-48)/2;
    Rectangle tb={(float)(mx+18),(float)(my+mh-52),(float)bW,40};
    Rectangle cb={(float)(mx+24+bW),(float)(my+mh-52),(float)bW,40};
    bool tHov=CheckCollisionPointRec(GetMousePosition(),tb);
    bool cHov=CheckCollisionPointRec(GetMousePosition(),cb);
    Color tbg=q->done?(Color){35,12,12,255}:(Color){12,35,18,255};
    Color tfg=q->done?C_RED:C_GREEN;
    QButton(tb,q->done?"Mark unfinished":"Mark finished",tbg,tfg,tHov);
    QButton(cb,"Close",(Color){30,22,55,255},Q_BORDER2,cHov);

    if(tHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
        q->done=!q->done; QuestSave();
        ShowNotif(q->done?"Quest completed":"Quest reopened");
    }
    if(cHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) quest_view=-1;
}

// ============================================================================
//  CHAIN QUESTS
//  - a chain is a small DAG of quest "nodes": each node can require several
//    parent nodes to be done (AND) before it unlocks, and can itself unlock
//    several child nodes once it's done.
//  - default creation builds a straight line (each step's only parent is the
//    previous one); "Advanced" creates just a root, which can then be grown
//    node-by-node (add a single child, or merge several nodes into one child)
//    from the graph view.
// ============================================================================

typedef struct {
    char name[MAX_QNAME];
    char desc[MAX_QDESC];
    int  cp, xp;
    bool done;
    int  parents [MAX_NODE_PARENTS];  int parentCount;
    int  children[MAX_NODE_CHILDREN]; int childCount;
} ChainNode;

typedef struct {
    char      name[MAX_QNAME];
    ChainNode nodes[MAX_CHAIN_NODES];
    int       nodeCount;
} QChain;

static QChain chains[MAX_CHAINS];
static int    chainCount = 0;
static int    chainView  = -1;          // index into chains[] open in the graph view, -1 = list
static float  chainListScroll  = 0.f;
static float  chainGraphScroll = 0.f;

// ---- Create-chain modal state ----
static bool  chain_create   = false;
static float chain_mscale   = 0.f;
static TBox  tbChainName    = {{0},false};
static char  ccs_name[MAX_QNAME] = {0};
static char  ccs_desc[MAX_QDESC] = {0};
static char  ccs_cp[MAX_QNUM]    = {0};
static char  ccs_xp[MAX_QNUM]    = {0};
static int   ccs_focus = 0;

typedef struct { char name[MAX_QNAME]; char desc[MAX_QDESC]; int cp, xp; } StagedStep;
static StagedStep staged[MAX_CHAIN_NODES];
static int        stagedCount = 0;

// ---- Graph-view editing state (add single child / merge several into one) ----
static bool  addNodeFormOpen = false;
static int   addNodeParents[MAX_NODE_PARENTS];
static int   addNodeParentCount = 0;
static char  an_name[MAX_QNAME] = {0};
static char  an_desc[MAX_QDESC] = {0};
static char  an_cp[MAX_QNUM]    = {0};
static char  an_xp[MAX_QNUM]    = {0};
static int   an_focus = 0;

static bool  mergeMode = false;
static bool  mergeSelected[MAX_CHAIN_NODES];

static int   deleteConfirmNode = -1;   // graph view: node index showing the delete choice, or -1

bool ChainModalOpen(void){ return chain_create || addNodeFormOpen; }

void ChainHandleEscape(void)
{
    if(addNodeFormOpen)     addNodeFormOpen=false;
    else if(chain_create)   chain_create=false;
}

static bool ChainNodeUnlocked(const QChain *c, int ni)
{
    const ChainNode *n=&c->nodes[ni];
    for(int i=0;i<n->parentCount;i++)
        if(!c->nodes[n->parents[i]].done) return false;
    return true;
}

static int ChainDoneCount(const QChain *c)
{
    int n=0;
    for(int i=0;i<c->nodeCount;i++) if(c->nodes[i].done) n++;
    return n;
}

// ---- Node / branch deletion (structural helpers; see ChainAfterDelete below for saving) ----

// Recomputes every node's children[] from the current parents[] lists.
static void ChainRebuildChildren(QChain *c)
{
    for(int ni=0;ni<c->nodeCount;ni++) c->nodes[ni].childCount=0;
    for(int ni=0;ni<c->nodeCount;ni++){
        ChainNode *n=&c->nodes[ni];
        for(int p=0;p<n->parentCount;p++){
            ChainNode *par=&c->nodes[n->parents[p]];
            if(par->childCount<MAX_NODE_CHILDREN) par->children[par->childCount++]=ni;
        }
    }
}

// Drops any reference to `removed` from every node's parents[], and shifts
// down any parent index above it so they still point at the right node
// after `removed` is spliced out of the nodes array.
static void ChainRemoveNodeRefs(QChain *c, int removed)
{
    for(int i=0;i<c->nodeCount;i++){
        ChainNode *n=&c->nodes[i];
        int w=0;
        for(int p=0;p<n->parentCount;p++){
            int val=n->parents[p];
            if(val==removed) continue;
            if(val>removed) val--;
            n->parents[w++]=val;
        }
        n->parentCount=w;
    }
}

// Deletes just this one node. Any child that depended only on it becomes a
// root itself (loses that one prerequisite); other parents are untouched.
static void ChainDeleteNode(QChain *c, int ni)
{
    for(int i=ni;i<c->nodeCount-1;i++) c->nodes[i]=c->nodes[i+1];
    c->nodeCount--;
    ChainRemoveNodeRefs(c,ni);
    ChainRebuildChildren(c);
}

static void ChainCollectDescendants(QChain *c, int ni, bool *mark)
{
    if(mark[ni]) return;
    mark[ni]=true;
    ChainNode *n=&c->nodes[ni];
    for(int k=0;k<n->childCount;k++) ChainCollectDescendants(c,n->children[k],mark);
}

// Deletes this node and every quest it (transitively) unlocks.
static void ChainDeleteBranch(QChain *c, int ni)
{
    bool mark[MAX_CHAIN_NODES]={false};
    ChainCollectDescendants(c,ni,mark);
    for(int i=c->nodeCount-1;i>=0;i--)
        if(mark[i]) ChainDeleteNode(c,i);
}

// ---- Persistence ----
static void ChainSave(void)
{
    FILE *f=fopen(CHAIN_SAVE_FILE,"w");
    if(!f){ ShowNotif("ERROR: Could not save chain quests!"); return; }
    fprintf(f,"# Aetheria Chain Quests\n");
    for(int ci=0;ci<chainCount;ci++){
        QChain *c=&chains[ci];
        fprintf(f,"CHAIN\nname=%s\n",c->name);
        for(int ni=0;ni<c->nodeCount;ni++){
            ChainNode *n=&c->nodes[ni];
            fprintf(f,"NODE\nname=%s\ndesc=%s\ncp=%d\nxp=%d\ndone=%d\nparents=",
                    n->name,n->desc,n->cp,n->xp,n->done?1:0);
            for(int p=0;p<n->parentCount;p++) fprintf(f,"%d%s",n->parents[p],p<n->parentCount-1?",":"");
            fprintf(f,"\n---\n");
        }
        fprintf(f,"ENDCHAIN\n");
    }
    fclose(f);
}

void ChainLoad(void)
{
    chainCount=0;
    FILE *f=fopen(CHAIN_SAVE_FILE,"r");
    if(!f) return;
    char line[512];
    QChain *cc=NULL; ChainNode *cn=NULL;
    while(fgets(line,sizeof(line),f)){
        int n=(int)strlen(line);
        while(n>0&&(line[n-1]=='\n'||line[n-1]=='\r')) line[--n]='\0';
        if(line[0]=='#') continue;
        if(strcmp(line,"CHAIN")==0){
            if(chainCount<MAX_CHAINS){ cc=&chains[chainCount++]; memset(cc,0,sizeof(QChain)); }
            else cc=NULL;
            cn=NULL;
        }
        else if(cc && !cn && strncmp(line,"name=",5)==0){
            strncpy(cc->name,line+5,MAX_QNAME-1);
        }
        else if(cc && strcmp(line,"NODE")==0){
            if(cc->nodeCount<MAX_CHAIN_NODES){ cn=&cc->nodes[cc->nodeCount++]; memset(cn,0,sizeof(ChainNode)); }
            else cn=NULL;
        }
        else if(cn && strncmp(line,"name=",5)==0)  strncpy(cn->name,line+5,MAX_QNAME-1);
        else if(cn && strncmp(line,"desc=",5)==0)  strncpy(cn->desc,line+5,MAX_QDESC-1);
        else if(cn && strncmp(line,"cp=",3)==0)    cn->cp=atoi(line+3);
        else if(cn && strncmp(line,"xp=",3)==0)    cn->xp=atoi(line+3);
        else if(cn && strncmp(line,"done=",5)==0)  cn->done=(atoi(line+5)==1);
        else if(cn && strncmp(line,"parents=",8)==0){
            char *p=line+8;
            while(*p && cn->parentCount<MAX_NODE_PARENTS){
                cn->parents[cn->parentCount++]=atoi(p);
                char *comma=strchr(p,',');
                if(!comma) break;
                p=comma+1;
            }
        }
        else if(strcmp(line,"---")==0)     cn=NULL;
        else if(strcmp(line,"ENDCHAIN")==0){ cc=NULL; cn=NULL; }
    }
    fclose(f);

    for(int ci=0;ci<chainCount;ci++) ChainRebuildChildren(&chains[ci]);
}

// After a delete, drop the whole chain entry if it's now empty and back out to the list.
static void ChainAfterDelete(int chainIdx)
{
    if(chains[chainIdx].nodeCount==0){
        for(int i=chainIdx;i<chainCount-1;i++) chains[i]=chains[i+1];
        chainCount--;
        chainView=-1;
    }
    ChainSave();
}

#define CHAIN_ROW_H 54

// Collapsed list: just the chain names + progress. Click one to open its graph.
static void DrawChainList(int cx, int cy, int cw, int listH)
{
    Vector2 mp=GetMousePosition();
    if(mp.x>cx&&mp.x<cx+cw&&mp.y>cy&&mp.y<cy+listH){
        float wh=GetMouseWheelMove();
        if(wh!=0){
            chainListScroll-=(int)(wh*22);
            float maxS=fmaxf(0.f,(float)(chainCount*CHAIN_ROW_H-listH));
            if(chainListScroll<0.f) chainListScroll=0.f;
            if(chainListScroll>maxS) chainListScroll=maxS;
        }
    }

    BeginScissorMode(cx,cy,cw,listH);
    if(chainCount==0) DrawText("No chain quests yet — create one below.",cx+16,cy+20,14,Q_TDARK);

    for(int i=0;i<chainCount;i++){
        QChain *c=&chains[i];
        int ry=cy+i*CHAIN_ROW_H-(int)chainListScroll;
        if(ry+CHAIN_ROW_H<cy||ry>cy+listH) continue;

        Rectangle row={(float)cx,(float)ry,(float)cw,CHAIN_ROW_H-4};
        bool hov=CheckCollisionPointRec(mp,row);
        int done=ChainDoneCount(c);
        bool complete=(c->nodeCount>0 && done==c->nodeCount);

        DrawRectangleRounded(row,0.06f,4,complete?Q_DONEBG:(hov?Q_HOVER:C_PANEL));
        DrawRectangleRoundedLinesEx(row,0.06f,4,1.f,complete?(Color){20,80,40,255}:(hov?Q_BORDER2:C_BORDER));
        DrawRectangle(cx,ry,4,CHAIN_ROW_H-4,complete?C_GREEN:Q_GPALE);

        DrawText(c->name,cx+18,ry+8,16,complete?Q_TDIM:Q_GPALE);
        char prog[32]; snprintf(prog,sizeof(prog),"%d / %d completed",done,c->nodeCount);
        DrawText(prog,cx+18,ry+28,12,Q_TDIM);

        DrawText(">",cx+cw-28,ry+14,20,hov?C_GOLD_L:C_MUTED);

        if(hov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ chainView=i; chainGraphScroll=0.f; mergeMode=false; deleteConfirmNode=-1; }
    }
    EndScissorMode();
}

static void ResetChainCreateBuffers(void)
{
    memset(tbChainName.buf,0,sizeof(tbChainName.buf));
    tbChainName.active=false;
    memset(ccs_name,0,MAX_QNAME); memset(ccs_desc,0,MAX_QDESC);
    memset(ccs_cp,0,MAX_QNUM);    memset(ccs_xp,0,MAX_QNUM);
    ccs_focus=0; stagedCount=0;
}

// "New Chain" modal: default builds a straight line from staged steps;
// Advanced skips straight to a single root node, grown later from the graph view.
static void DrawChainCreateModal(float dt)
{
    chain_mscale+=dt*6.f; if(chain_mscale>1.f) chain_mscale=1.f;
    float s=chain_mscale; s=s*s*(3-2*s);

    DrawRectangle(0,0,900,600,Q_OVERLAY);

    int mw=580, mh=520;
    int rw=(int)(mw*s), rh=(int)(mh*s);
    int rx=(int)(450-rw/2), ry=(int)(300-rh/2);
    if(rw<10||rh<10) return;
    int mx=(900-mw)/2, my=(600-mh)/2;

    Rectangle modal={(float)rx,(float)ry,(float)rw,(float)rh};
    DrawRectangleRounded(modal,0.06f,8,(Color){15,11,32,255});
    DrawRectangleRoundedLinesEx(modal,0.06f,8,1.5f,Q_BORDER2);
    if(s<0.8f) return;

    DrawRectangleRounded((Rectangle){(float)mx,(float)my,(float)mw,46},0.06f,6,(Color){25,18,50,255});
    DrawText("New Chain Quest",mx+18,my+13,18,Q_GPALE);

    Rectangle closeR={(float)(mx+mw-40),(float)(my+8),30,30};
    bool cHov=CheckCollisionPointRec(GetMousePosition(),closeR);
    DrawRectangleRounded(closeR,0.3f,4,cHov?(Color){60,20,20,255}:C_PANEL2);
    DrawXMark(closeR,2.f,C_RED);
    if(cHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) chain_create=false;

    int fy=my+58;

    Rectangle nameR={(float)(mx+18),(float)(fy+18),(float)(mw-36),34};
    TBoxTick(&tbChainName,nameR);
    TBoxDraw(&tbChainName,nameR,"Chain Name  -  e.g.  The Lost Heir");
    fy+=64;

    const char *stepHint = "Add steps below to build the chain, in order.";
    DrawText(stepHint, mx+mw-18-MeasureText(stepHint,12), fy+8, 12, C_MUTED);
    fy+=20;

    Rectangle stepNameR={(float)(mx+18),(float)(fy+18),(float)(mw-36),32};
    if(CheckCollisionPointRec(GetMousePosition(),stepNameR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) ccs_focus=0;
    QInputBox(stepNameR, "Step Name", ccs_name, ccs_focus==0);
    if(ccs_focus==0) TypeInto(ccs_name,MAX_QNAME,false,dt);
    fy+=64;

    Rectangle stepDescR={(float)(mx+18),(float)(fy+18),(float)(mw-36),48};
    if(CheckCollisionPointRec(GetMousePosition(),stepDescR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) ccs_focus=1;
    QInputBox(stepDescR,"Description",ccs_desc,ccs_focus==1);
    if(ccs_focus==1) TypeInto(ccs_desc,MAX_QDESC,false,dt);
    fy+=82;

    int hw2=(mw-36)/2-6;
    Rectangle cpR={(float)(mx+18),(float)(fy+18),(float)hw2,32};
    Rectangle xpR={(float)(mx+18+hw2+12),(float)(fy+18),(float)hw2,32};
    if(CheckCollisionPointRec(GetMousePosition(),cpR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) ccs_focus=2;
    if(CheckCollisionPointRec(GetMousePosition(),xpR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) ccs_focus=3;
    QInputBox(cpR,"CP Reward",ccs_cp,ccs_focus==2);
    QInputBox(xpR,"XP Reward",ccs_xp,ccs_focus==3);
    if(ccs_focus==2) TypeInto(ccs_cp,MAX_QNUM,true,dt);
    if(ccs_focus==3) TypeInto(ccs_xp,MAX_QNUM,true,dt);
    if(IsKeyPressed(KEY_TAB)) ccs_focus=(ccs_focus+1)%4;
    fy+=66;

    Rectangle addR={(float)(mx+18),(float)fy,(float)(mw-36),32};
    bool addHov=CheckCollisionPointRec(GetMousePosition(),addR);
    QButton(addR,"+ Add Step to Chain",(Color){20,45,20,255},C_GREEN_L,addHov);
    if(addHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
        if(!strlen(ccs_name)) ShowNotif("Please enter a step name.");
        else if(stagedCount>=MAX_CHAIN_NODES) ShowNotif("Chain is full.");
        else {
            StagedStep *ss=&staged[stagedCount++];
            memset(ss,0,sizeof(*ss));
            strncpy(ss->name,ccs_name,MAX_QNAME-1);
            strncpy(ss->desc,ccs_desc,MAX_QDESC-1);
            ss->cp=atoi(ccs_cp); ss->xp=atoi(ccs_xp);
            memset(ccs_name,0,MAX_QNAME); memset(ccs_desc,0,MAX_QDESC);
            memset(ccs_cp,0,MAX_QNUM);    memset(ccs_xp,0,MAX_QNUM);
            ccs_focus=0;
        }
    }
    fy+=42;

    DrawText(TextFormat("Steps: %d",stagedCount),mx+18,fy,12,stagedCount>0?C_GREEN_L:C_MUTED);
    fy+=18;
    int listY=fy, listH=(my+mh-52)-listY;
    BeginScissorMode(mx+18,listY,mw-36,listH);
    ry=listY;
    for(int i=0;i<stagedCount;i++){
        Rectangle rr={(float)(mx+18),(float)ry,(float)(mw-36),26};
        DrawRectangleRounded(rr,0.12f,6,i%2?C_PANEL:C_PANEL2);
        DrawText(TextFormat("%d. %s",i+1,staged[i].name),(int)rr.x+8,(int)rr.y+5,13,C_TEXT);
        Rectangle rmR={(float)(rr.x+rr.width-60),(float)rr.y+2,54,22};
        bool rmHov=CheckCollisionPointRec(GetMousePosition(),rmR);
        QButton(rmR,"Remove",(Color){40,10,10,255},C_RED,rmHov);
        if(rmHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
            for(int k=i;k<stagedCount-1;k++) staged[k]=staged[k+1];
            stagedCount--;
        }
        ry+=28;
    }
    EndScissorMode();

    Rectangle okR={(float)(mx+18),(float)(my+mh-46),(float)(mw-36)/2-6,38};
    Rectangle cnR={(float)(mx+18+(mw-36)/2+6),(float)(my+mh-46),(float)(mw-36)/2-6,38};
    bool okH=CheckCollisionPointRec(GetMousePosition(),okR);
    bool cnH=CheckCollisionPointRec(GetMousePosition(),cnR);
    QButton(okR,"Create Chain",(Color){20,45,20,255},C_GREEN,okH);
    QButton(cnR,"Cancel",(Color){40,10,10,255},C_RED,cnH);
    if(cnH&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) chain_create=false;

    if(okH&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
        if(!strlen(tbChainName.buf))     ShowNotif("Please enter a chain name.");
        else if(chainCount>=MAX_CHAINS)  ShowNotif("Chain register is full.");
        else if(stagedCount==0)          ShowNotif("Add at least one step.");
        else {
            QChain *c=&chains[chainCount++];
            memset(c,0,sizeof(*c));
            strncpy(c->name,tbChainName.buf,MAX_QNAME-1);

            for(int i=0;i<stagedCount;i++){
                ChainNode *n=&c->nodes[i];
                strncpy(n->name,staged[i].name,MAX_QNAME-1);
                strncpy(n->desc,staged[i].desc,MAX_QDESC-1);
                n->cp=staged[i].cp; n->xp=staged[i].xp;
                if(i>0){
                    n->parents[0]=i-1; n->parentCount=1;
                    c->nodes[i-1].children[c->nodes[i-1].childCount++]=i;
                }
            }
            c->nodeCount=stagedCount;

            ChainSave();
            ShowNotif(TextFormat("Chain '%s' created!",c->name));
            chain_create=false;
            ResetChainCreateBuffers();
        }
    }
}

// Small modal used both for "add single child" and "combine selected into a new quest" —
// they're the same operation with a different parent set.
static void DrawAddNodeModal(float dt)
{
    if(chainView<0||chainView>=chainCount) return;
    QChain *c=&chains[chainView];

    DrawRectangle(0,0,900,600,Q_OVERLAY);

    int mw=460, mh=340;
    int mx=(900-mw)/2, my=(600-mh)/2;
    Rectangle modal={(float)mx,(float)my,(float)mw,(float)mh};
    DrawRectangleRounded(modal,0.07f,8,(Color){14,11,30,255});
    DrawRectangleRoundedLinesEx(modal,0.07f,8,1.5f,Q_GDIM);

    DrawRectangleRounded((Rectangle){(float)mx,(float)my,(float)mw,44},0.07f,6,(Color){22,17,48,255});
    const char *ttl = addNodeParentCount>1 ? "New Quest (from selection)" : "Unlock New Quest";
    DrawText(ttl,mx+16,my+12,16,Q_GPALE);

    int py=my+56;
    DrawText("Requires:",mx+16,py,12,Q_TDIM); py+=16;
    for(int i=0;i<addNodeParentCount;i++){
        DrawText(TextFormat("- %s",c->nodes[addNodeParents[i]].name),mx+20,py,13,C_TEXT);
        py+=17;
    }
    py+=6;

    Rectangle nameR={(float)(mx+16),(float)(py+18),(float)(mw-32),32};
    if(CheckCollisionPointRec(GetMousePosition(),nameR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) an_focus=0;
    QInputBox(nameR,"Quest Name",an_name,an_focus==0);
    if(an_focus==0) TypeInto(an_name,MAX_QNAME,false,dt);
    py+=64;

    Rectangle descR={(float)(mx+16),(float)(py+18),(float)(mw-32),44};
    if(CheckCollisionPointRec(GetMousePosition(),descR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) an_focus=1;
    QInputBox(descR,"Description",an_desc,an_focus==1);
    if(an_focus==1) TypeInto(an_desc,MAX_QDESC,false,dt);
    py+=78;

    int hw2=(mw-32)/2-6;
    Rectangle cpR={(float)(mx+16),(float)(py+18),(float)hw2,32};
    Rectangle xpR={(float)(mx+16+hw2+12),(float)(py+18),(float)hw2,32};
    if(CheckCollisionPointRec(GetMousePosition(),cpR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) an_focus=2;
    if(CheckCollisionPointRec(GetMousePosition(),xpR)&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) an_focus=3;
    QInputBox(cpR,"CP Reward",an_cp,an_focus==2);
    QInputBox(xpR,"XP Reward",an_xp,an_focus==3);
    if(an_focus==2) TypeInto(an_cp,MAX_QNUM,true,dt);
    if(an_focus==3) TypeInto(an_xp,MAX_QNUM,true,dt);
    if(IsKeyPressed(KEY_TAB)) an_focus=(an_focus+1)%4;

    Rectangle okR={(float)(mx+16),(float)(my+mh-46),(float)(mw-32)/2-6,36};
    Rectangle cnR={(float)(mx+16+(mw-32)/2+6),(float)(my+mh-46),(float)(mw-32)/2-6,36};
    bool okH=CheckCollisionPointRec(GetMousePosition(),okR);
    bool cnH=CheckCollisionPointRec(GetMousePosition(),cnR);
    QButton(okR,"Create",(Color){20,45,20,255},C_GREEN,okH);
    QButton(cnR,"Cancel",(Color){40,10,10,255},C_RED,cnH);
    if(cnH&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) addNodeFormOpen=false;

    if(okH&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
        if(!strlen(an_name))                  ShowNotif("Please enter a quest name.");
        else if(c->nodeCount>=MAX_CHAIN_NODES) ShowNotif("This chain is full.");
        else {
            int ni=c->nodeCount++;
            ChainNode *n=&c->nodes[ni];
            memset(n,0,sizeof(*n));
            strncpy(n->name,an_name,MAX_QNAME-1);
            strncpy(n->desc,an_desc,MAX_QDESC-1);
            n->cp=atoi(an_cp); n->xp=atoi(an_xp);
            for(int i=0;i<addNodeParentCount;i++){
                n->parents[n->parentCount++]=addNodeParents[i];
                ChainNode *par=&c->nodes[addNodeParents[i]];
                if(par->childCount<MAX_NODE_CHILDREN) par->children[par->childCount++]=ni;
            }
            ChainSave();
            ShowNotif("New quest unlocked in the chain!");
            addNodeFormOpen=false;
            mergeMode=false;
            for(int i=0;i<MAX_CHAIN_NODES;i++) mergeSelected[i]=false;
        }
    }
}

void DrawChainModals(float dt)
{
    if(chain_create)            DrawChainCreateModal(dt);
    else if(addNodeFormOpen)    DrawAddNodeModal(dt);
}

#define CHAIN_BOX_W 150
#define CHAIN_BOX_H  64
#define CHAIN_GAP_X  20
#define CHAIN_GAP_Y  46

// Depth of each node = 1 + the deepest of its parents (0 for roots). A few
// relaxation passes over the small node set is enough to settle any DAG.
static void ComputeChainTiers(QChain *c, int tier[MAX_CHAIN_NODES])
{
    for(int i=0;i<c->nodeCount;i++) tier[i]=0;
    for(int pass=0;pass<c->nodeCount;pass++){
        for(int i=0;i<c->nodeCount;i++){
            ChainNode *n=&c->nodes[i];
            if(n->parentCount==0) continue;
            int mt=0;
            for(int p=0;p<n->parentCount;p++) if(tier[n->parents[p]]+1>mt) mt=tier[n->parents[p]]+1;
            if(mt>tier[i]) tier[i]=mt;
        }
    }
}

// Graphical node view: nodes laid out in tiers (rows) by dependency depth,
// connected with lines. Done = green, unlocked = gold, locked = grey/no interaction.
static void DrawChainGraphView(int cX, int cY, int cw)
{
    QChain *c=&chains[chainView];

    DrawText(c->name,cX,cY,20,C_GOLD_L);
    Rectangle backR={(float)(cX+cw-88),(float)(cY-2),88.f,26.f};
    if(SmallBtn(backR,"< Back",C_PANEL3,C_MUTED)){ chainView=-1; mergeMode=false; deleteConfirmNode=-1; }
    int done=ChainDoneCount(c);
    DrawText(TextFormat("%d / %d completed",done,c->nodeCount),cX,cY+24,13,C_MUTED);
    HDivider(cX,cY+42,cw);

    int hy=cY+50;
    int selCount=0; for(int i=0;i<c->nodeCount;i++) if(mergeSelected[i]) selCount++;

    Rectangle mergeBtn={(float)cX,(float)hy,150.f,26.f};
    if(SmallBtn(mergeBtn, mergeMode?"Cancel Merge":"Combine Quests", mergeMode?C_GOLD_D:C_PANEL3, mergeMode?C_GOLD_L:C_MUTED)){
        mergeMode=!mergeMode;
        for(int i=0;i<MAX_CHAIN_NODES;i++) mergeSelected[i]=false;
        deleteConfirmNode=-1;
    }
    if(mergeMode){
        const char *hint = selCount>=2 ? "Pick quests, then create the new one." : "Select at least 2 quests to combine.";
        DrawText(hint, cX+160, hy+6, 12, C_MUTED);
        if(selCount>=2){
            Rectangle useR={(float)(cX+cw-190),(float)hy,190.f,26.f};
            if(SmallBtn(useR,"Create From Selection",C_GOLD_D,C_GOLD_L)){
                addNodeParentCount=0;
                for(int i=0;i<c->nodeCount && addNodeParentCount<MAX_NODE_PARENTS;i++)
                    if(mergeSelected[i]) addNodeParents[addNodeParentCount++]=i;
                memset(an_name,0,MAX_QNAME); memset(an_desc,0,MAX_QDESC);
                memset(an_cp,0,MAX_QNUM);    memset(an_xp,0,MAX_QNUM);
                an_focus=0; addNodeFormOpen=true;
            }
        }
    }
    hy+=36;

    int tier[MAX_CHAIN_NODES];
    ComputeChainTiers(c,tier);
    int maxTier=0;
    for(int i=0;i<c->nodeCount;i++) if(tier[i]>maxTier) maxTier=tier[i];

    int tierCount[MAX_CHAIN_NODES+1]={0};
    for(int i=0;i<c->nodeCount;i++) tierCount[tier[i]]++;
    int tierFill[MAX_CHAIN_NODES+1]={0};

    int listY=hy, listH=566-listY;

    if(CheckCollisionPointRec(GetMousePosition(),(Rectangle){(float)cX,(float)listY,(float)cw,(float)listH}))
        chainGraphScroll -= GetMouseWheelMove()*40.f;
    int totalH=(maxTier+1)*(CHAIN_BOX_H+CHAIN_GAP_Y);
    float maxS=fmaxf(0.f,(float)(totalH-listH));
    if(chainGraphScroll<0.f) chainGraphScroll=0.f;
    if(chainGraphScroll>maxS) chainGraphScroll=maxS;

    Rectangle box[MAX_CHAIN_NODES];
    for(int i=0;i<c->nodeCount;i++){
        int t=tier[i], cnt=tierCount[t];
        int rowW=cnt*CHAIN_BOX_W+(cnt-1)*CHAIN_GAP_X;
        int startX=cX+(cw-rowW)/2; if(startX<cX) startX=cX;
        int idxInRow=tierFill[t]++;
        int bx=startX+idxInRow*(CHAIN_BOX_W+CHAIN_GAP_X);
        int by=listY+t*(CHAIN_BOX_H+CHAIN_GAP_Y)-(int)chainGraphScroll;
        box[i]=(Rectangle){(float)bx,(float)by,(float)CHAIN_BOX_W,(float)CHAIN_BOX_H};
    }

    BeginScissorMode(cX-4,listY,cw+8,listH+4);

    for(int i=0;i<c->nodeCount;i++){
        ChainNode *n=&c->nodes[i];
        for(int p=0;p<n->parentCount;p++){
            Rectangle *pb=&box[n->parents[p]];
            Rectangle *cb=&box[i];
            Color lc = n->done?C_GREEN:(ChainNodeUnlocked(c,i)?C_GOLD:C_BORDER);
            Vector2 s1={pb->x+pb->width/2, pb->y+pb->height};
            Vector2 s2={cb->x+cb->width/2, cb->y};
            DrawLineEx(s1,s2,2.f,lc);
        }
    }

    for(int i=0;i<c->nodeCount;i++){
        ChainNode *n=&c->nodes[i];
        Rectangle b=box[i];
        if(b.y+b.height<listY-40||b.y>listY+listH+40) continue;

        bool unlocked=ChainNodeUnlocked(c,i);
        bool sel=mergeSelected[i];
        Color bg = n->done?(Color){20,45,24,255}:unlocked?C_PANEL3:C_PANEL2;
        Color bd = sel?C_BLUE_L:n->done?C_GREEN:unlocked?C_GOLD:C_BORDER;

        DrawRectangleRounded(b,0.10f,8,bg);
        DrawRectangleRoundedLinesEx(b,0.10f,8,sel?2.5f:1.5f,bd);
        DrawRectangle((int)b.x,(int)b.y,4,(int)b.height, n->done?C_GREEN:unlocked?C_GOLD:C_MUTED);

        char nm[32]; strncpy(nm,n->name,31); nm[31]='\0';
        while(MeasureText(nm,13)>CHAIN_BOX_W-30&&strlen(nm)>0) nm[strlen(nm)-1]='\0';
        DrawText(nm,(int)b.x+10,(int)b.y+8,13, n->done?Q_TDIM:(unlocked?C_TEXT:C_MUTED));

        if(deleteConfirmNode==i){
            // Inline "delete this node?" choice, replacing the usual bottom row.
            DrawText("Delete:",(int)b.x+10,(int)b.y+26,11,C_RED_L);
            Rectangle oneR={b.x+8, b.y+b.height-24,60,20};
            Rectangle allR={b.x+72,b.y+b.height-24,70,20};
            if(SmallBtn(oneR,"Node",C_RED,C_TEXT)){
                ChainDeleteNode(c,i);
                ChainAfterDelete(chainView);
                ShowNotif("Quest deleted.");
                deleteConfirmNode=-1;
                break;   // node array shifted — stop drawing this frame's boxes
            }
            if(SmallBtn(allR,"Branch",C_RED,C_TEXT)){
                ChainDeleteBranch(c,i);
                ChainAfterDelete(chainView);
                ShowNotif("Quest and its branch deleted.");
                deleteConfirmNode=-1;
                break;
            }
        } else {
            DrawText(TextFormat("%d CP  %d XP",n->cp,n->xp),(int)b.x+10,(int)b.y+26,11,Q_TDIM);

            if(!unlocked && !n->done){
                DrawText("Locked",(int)b.x+30,(int)b.y+44,11,C_MUTED);
            } else {
                Rectangle cb2={b.x+b.width-28,b.y+b.height-24,20,20};
                bool cbHov=CheckCollisionPointRec(GetMousePosition(),cb2);
                DrawRectangleRec(cb2,Q_INBG);
                DrawRectangleLinesEx(cb2,1.5f,n->done?C_GREEN:Q_BORDER2);
                if(n->done) DrawXMark(cb2,2.f,C_GREEN);
                if(!mergeMode && cbHov && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
                    n->done=!n->done; ChainSave();
                    ShowNotif(n->done?"Quest completed!":"Quest reopened.");
                }
            }

            if(!mergeMode){
                Rectangle plusR={b.x+4,b.y+b.height-24,20,20};
                bool plusHov=CheckCollisionPointRec(GetMousePosition(),plusR);
                DrawRectangleRounded(plusR,0.3f,4,plusHov?C_GOLD_D:C_PANEL2);
                DrawText("+",(int)plusR.x+6,(int)plusR.y+1,16,plusHov?C_GOLD_L:C_MUTED);
                if(plusHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
                    addNodeParentCount=1; addNodeParents[0]=i;
                    memset(an_name,0,MAX_QNAME); memset(an_desc,0,MAX_QDESC);
                    memset(an_cp,0,MAX_QNUM);    memset(an_xp,0,MAX_QNUM);
                    an_focus=0; addNodeFormOpen=true;
                }
            }
        }

        bool boxHov=CheckCollisionPointRec(GetMousePosition(),b);
        if(mergeMode && boxHov && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) mergeSelected[i]=!mergeSelected[i];

        if(!mergeMode){
            // Delete trigger, top-right corner; click again (or pick an option above) to back out.
            Rectangle delR={b.x+b.width-20,b.y+4,16,16};
            bool delHov=CheckCollisionPointRec(GetMousePosition(),delR);
            DrawRectangleRounded(delR,0.3f,4,delHov?(Color){60,20,20,255}:C_PANEL2);
            DrawXMark(delR,1.5f,C_RED);
            if(delHov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
                deleteConfirmNode = (deleteConfirmNode==i) ? -1 : i;
        }
    }
    EndScissorMode();
}

// Quests tab entry point
void DrawTabQuests(int cX, int cY, float dt, int playerLevel)
{
    int cw=660;

    DrawText("QUESTS",cX,cY,22,C_GOLD_L);
    HDivider(cX,cY+34,cw);

    const char *qtabs[]={"Quests","Story Quests","Chain Quests","Tree Quests"};
    int tabW=(cw-12)/4;
    for(int i=0;i<4;i++){
        int tx=cX+i*(tabW+4), ty=cY+40;
        Rectangle tr={(float)tx,(float)ty,(float)tabW,26};
        bool active=(quest_tab==i);
        bool hov=CheckCollisionPointRec(GetMousePosition(),tr)&&!quest_create&&quest_view<0&&!ChainModalOpen();
        DrawRectangleRounded(tr,0.25f,6,active?C_PANEL3:(hov?C_PANEL2:C_PANEL));
        DrawRectangleRoundedLinesEx(tr,0.25f,6,1.f,active?C_GOLD:C_BORDER);
        if(active) DrawRectangle(tx,ty,tabW,3,C_GOLD);
        int tw2=MeasureText(qtabs[i],12);
        DrawText(qtabs[i],tx+(tabW-tw2)/2,ty+7,12,active?C_GOLD_L:C_MUTED);
        if(hov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ quest_tab=i; quest_scroll=0; }
    }

    int listY=cY+74;
    int createH=38;
    int createY=572-createH-6;
    int listH=createY-4-listY;

    if(quest_tab==0||quest_tab==1){
        DrawQuestList(cX,listY,cw,listH,quest_tab);

        if(quest_tab==0){
            // "Quests" only: New Quest + Generate Quest side by side
            int halfW=(cw-8)/2;
            Rectangle nb={(float)cX,(float)createY,(float)halfW,(float)createH};
            Rectangle gb={(float)(cX+halfW+8),(float)createY,(float)halfW,(float)createH};

            bool nhov=CheckCollisionPointRec(GetMousePosition(),nb)&&!quest_create&&quest_view<0;
            DrawRectangleRounded(nb,0.2f,6,(Color){25,18,50,255});
            DrawRectangleRoundedLinesEx(nb,0.2f,6,1.f,nhov?C_GOLD:Q_BORDER2);
            const char *lbl="+ New Quest";
            int lw=MeasureText(lbl,15);
            DrawText(lbl,(int)(nb.x+nb.width/2-lw/2),(int)(nb.y+11),15,nhov?C_GOLD_L:Q_TDIM);
            if(nhov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
                quest_create=true;
                ResetQuestBuilder(playerLevel);
            }

            bool ghov=CheckCollisionPointRec(GetMousePosition(),gb)&&!quest_create&&quest_view<0;
            DrawRectangleRounded(gb,0.2f,6,(Color){20,40,55,255});
            DrawRectangleRoundedLinesEx(gb,0.2f,6,1.f,ghov?C_BLUE_L:Q_BORDER2);
            const char *glbl="Generate Quest";
            int glw=MeasureText(glbl,15);
            DrawText(glbl,(int)(gb.x+gb.width/2-glw/2),(int)(gb.y+11),15,ghov?C_BLUE_L:Q_TDIM);
            if(ghov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) GenerateQuest(playerLevel);
        } else {
            Rectangle nb={(float)cX,(float)createY,(float)cw,(float)createH};
            bool nhov=CheckCollisionPointRec(GetMousePosition(),nb)&&!quest_create&&quest_view<0;
            DrawRectangleRounded(nb,0.2f,6,(Color){25,18,50,255});
            DrawRectangleRoundedLinesEx(nb,0.2f,6,1.f,nhov?C_GOLD:Q_BORDER2);
            const char *lbl="+ New Story-Quest";
            int lw=MeasureText(lbl,15);
            DrawText(lbl,(int)(nb.x+nb.width/2-lw/2),(int)(nb.y+11),15,nhov?C_GOLD_L:Q_TDIM);
            if(nhov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
                quest_create=true; q_focus=0; q_mscale=0.f;
                memset(q_name,0,MAX_QNAME); memset(q_desc,0,MAX_QDESC);
                memset(q_cp,0,MAX_QNUM);    memset(q_xp,0,MAX_QNUM);
            }
        }
    } else if(quest_tab==2){
        if(chainView<0){
            DrawChainList(cX,listY,cw,listH);

            Rectangle nb={(float)cX,(float)createY,(float)cw,(float)createH};
            bool nhov=CheckCollisionPointRec(GetMousePosition(),nb)&&!ChainModalOpen();
            DrawRectangleRounded(nb,0.2f,6,(Color){25,18,50,255});
            DrawRectangleRoundedLinesEx(nb,0.2f,6,1.f,nhov?C_GOLD:Q_BORDER2);
            const char *lbl="+ New Chain";
            int lw=MeasureText(lbl,15);
            DrawText(lbl,(int)(nb.x+nb.width/2-lw/2),(int)(nb.y+11),15,nhov?C_GOLD_L:Q_TDIM);

            if(nhov&&IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){
                chain_create=true; chain_mscale=0.f;
                ResetChainCreateBuffers();
            }
        } else {
            DrawChainGraphView(cX,listY,cw);
        }
    } else {
        Panel((Rectangle){(float)cX,(float)listY,(float)cw,(float)(listH+createH+8)},C_PANEL2,C_BORDER);
        const char *nm="Tree Quests";
        int nmH=listH+createH+8;
        DrawText(nm, cX+(cw-MeasureText(nm,18))/2, listY+nmH/2-20,18,C_GOLD_L);
        const char *cs="Coming soon...";
        DrawText(cs, cX+(cw-MeasureText(cs,14))/2, listY+nmH/2+8, 14,C_MUTED);
    }

    (void)dt;
}
