#include <fs.h>
#include <serial.h>
#include <memory/heap.h>
#include <string_utils.h>

extern EFI_SYSTEM_TABLE *ST;

#pragma pack(push, 1)

struct BPB {
    UINT8  jmpBoot[3];
    UINT8  OEMName[8];
    UINT16 BytsPerSec;
    UINT8  SecPerClus;
    UINT16 RsvdSecCnt;
    UINT8  NumFATs;
    UINT16 RootEntCnt;
    UINT16 TotSec16;
    UINT8  Media;
    UINT16 FATSz16;
    UINT16 SecPerTrk;
    UINT16 NumHeads;
    UINT32 HiddSec;
    UINT32 TotSec32;
    UINT32 FATSz32;
    UINT16 ExtFlags;
    UINT16 FSVer;
    UINT32 RootClus;
    UINT16 FSInfo;
    UINT16 BkBootSec;
    UINT8  Reserved[12];
    UINT8  DrvNum;
    UINT8  Reserved1;
    UINT8  BootSig;
    UINT32 VolID;
    UINT8  VolLab[11];
    UINT8  FilSysType[8];
};

struct GPTHeader {
    UINT64  Signature;
    UINT32  Revision;
    UINT32  HeaderSize;
    UINT32  HeaderCRC32;
    UINT32  Reserved;
    UINT64  MyLBA;
    UINT64  AlternateLBA;
    UINT64  FirstUsableLBA;
    UINT64  LastUsableLBA;
    EFI_GUID DiskGUID;
    UINT64  PartitionEntryLBA;
    UINT32  NumPartitionEntries;
    UINT32  SizeOfPartitionEntry;
    UINT32  PartitionEntryArrayCRC32;
};

struct GPTEntry {
    EFI_GUID PartitionTypeGUID;
    EFI_GUID UniquePartitionGUID;
    UINT64   StartingLBA;
    UINT64   EndingLBA;
    UINT64   Attributes;
    CHAR16   PartitionName[36];
};

struct MBRPart {
    UINT8   Status;
    UINT8   CHSStart[3];
    UINT8   Type;
    UINT8   CHSEnd[3];
    UINT32  LBABegin;
    UINT32  NumSectors;
};

#pragma pack(pop)

#define GPT_SIGNATURE_VAL 0x5452415020494645ULL
#define MBR_SIGNATURE     0xAA55

#define MBR_TYPE_FAT12    0x01
#define MBR_TYPE_FAT16    0x04
#define MBR_TYPE_FAT16B   0x06
#define MBR_TYPE_FAT32    0x0B
#define MBR_TYPE_FAT32LBA 0x0C
#define MBR_TYPE_FAT16LBA 0x0E
#define MBR_TYPE_ESP      0xEF

// ---- GUID helpers ----

static BOOLEAN guid_is_zero(EFI_GUID *g) {
    return g->Data1 == 0 && g->Data2 == 0 && g->Data3 == 0
        && g->Data4[0] == 0 && g->Data4[1] == 0
        && g->Data4[2] == 0 && g->Data4[3] == 0
        && g->Data4[4] == 0 && g->Data4[5] == 0
        && g->Data4[6] == 0 && g->Data4[7] == 0;
}

static BOOLEAN guid_eq(EFI_GUID *a, EFI_GUID *b) {
    return a->Data1 == b->Data1 && a->Data2 == b->Data2
        && a->Data3 == b->Data3
        && a->Data4[0] == b->Data4[0] && a->Data4[1] == b->Data4[1]
        && a->Data4[2] == b->Data4[2] && a->Data4[3] == b->Data4[3]
        && a->Data4[4] == b->Data4[4] && a->Data4[5] == b->Data4[5]
        && a->Data4[6] == b->Data4[6] && a->Data4[7] == b->Data4[7];
}

// ---- Block I/O ----

struct block_dev {
    EFI_BLOCK_IO_PROTOCOL *Bio;
    UINT32 BlockSize;
};

static EFI_STATUS blk_init(struct block_dev *dev) {
    EFI_GUID g = EFI_BLOCK_IO_PROTOCOL_GUID;
    EFI_HANDLE *Handles = NULL;
    UINTN NoHandles = 0;
    EFI_STATUS st = uefi_call_wrapper(ST->BootServices->LocateHandleBuffer, 5,
        ByProtocol, &g, NULL, &NoHandles, &Handles);
    if (EFI_ERROR(st) || NoHandles == 0) return EFI_NOT_FOUND;

    EFI_BLOCK_IO_PROTOCOL *Bio = NULL;
    for (UINTN i = 0; i < NoHandles; i++) {
        EFI_BLOCK_IO_PROTOCOL *b = NULL;
        st = uefi_call_wrapper(ST->BootServices->HandleProtocol, 3,
            Handles[i], &g, (void**)&b);
        if (EFI_ERROR(st)) continue;
        if (b->Media->MediaPresent) { Bio = b; break; }
        if (!Bio) Bio = b;
    }

    uefi_call_wrapper(ST->BootServices->FreePool, 1, Handles);
    if (!Bio) return EFI_NOT_FOUND;

    dev->Bio = Bio;
    dev->BlockSize = Bio->Media->BlockSize;
    return EFI_SUCCESS;
}

static EFI_STATUS blk_read(struct block_dev *dev, UINT64 LBA,
                            UINTN Sectors, void *Buf) {
    return uefi_call_wrapper(dev->Bio->ReadBlocks, 5,
        dev->Bio, dev->Bio->Media->MediaId, LBA,
        Sectors * dev->BlockSize, Buf);
}

static EFI_STATUS blk_write(struct block_dev *dev, UINT64 LBA,
                             UINTN Sectors, const void *Buf) {
    return uefi_call_wrapper(dev->Bio->WriteBlocks, 5,
        dev->Bio, dev->Bio->Media->MediaId, LBA,
        Sectors * dev->BlockSize, (void*)Buf);
}

// ---- Partition detection ----

static EFI_STATUS find_gpt_partition(struct block_dev *dev, UINT64 *StartLBA) {
    UINT8 *Buf = (UINT8*)kmalloc(dev->BlockSize);
    if (!Buf) return EFI_OUT_OF_RESOURCES;

    EFI_STATUS st = blk_read(dev, 1, 1, Buf);
    if (EFI_ERROR(st)) { kfree(Buf); return st; }

    struct GPTHeader *Hdr = (struct GPTHeader*)Buf;
    if (Hdr->Signature != GPT_SIGNATURE_VAL) { kfree(Buf); return EFI_NOT_FOUND; }

    // Read partition entries
    UINTN EntrySz = Hdr->SizeOfPartitionEntry;
    UINTN NumEnt = Hdr->NumPartitionEntries;
    UINTN Total = EntrySz * NumEnt;
    UINTN Secs = (Total + dev->BlockSize - 1) / dev->BlockSize;

    UINT8 *EntBuf = (UINT8*)kmalloc(Secs * dev->BlockSize);
    if (!EntBuf) { kfree(Buf); return EFI_OUT_OF_RESOURCES; }

    st = blk_read(dev, Hdr->PartitionEntryLBA, Secs, EntBuf);
    if (EFI_ERROR(st)) { kfree(Buf); kfree(EntBuf); return st; }

    // EFI System Partition GUID
    EFI_GUID esp = { 0xC12A7328, 0xF81F, 0x11D2,
        {0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B} };
    // Basic data partition GUID (FAT)
    EFI_GUID basic = { 0xEBD0A0A2, 0xB9E5, 0x4433,
        {0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7} };

    BOOLEAN found = FALSE;
    for (UINTN i = 0; i < NumEnt; i++) {
        struct GPTEntry *E = (struct GPTEntry*)(EntBuf + i * EntrySz);
        if (guid_is_zero(&E->PartitionTypeGUID)) continue;
        if (guid_eq(&E->PartitionTypeGUID, &esp) ||
            guid_eq(&E->PartitionTypeGUID, &basic)) {
            *StartLBA = E->StartingLBA;
            found = TRUE;
            break;
        }
    }

    if (!found) {
        // Fallback: first non-empty partition
        for (UINTN i = 0; i < NumEnt; i++) {
            struct GPTEntry *E = (struct GPTEntry*)(EntBuf + i * EntrySz);
            if (!guid_is_zero(&E->PartitionTypeGUID)) {
                *StartLBA = E->StartingLBA;
                found = TRUE;
                break;
            }
        }
    }

    kfree(Buf);
    kfree(EntBuf);
    return found ? EFI_SUCCESS : EFI_NOT_FOUND;
}

static EFI_STATUS find_mbr_partition(struct block_dev *dev, UINT64 *StartLBA) {
    UINT8 *Buf = (UINT8*)kmalloc(dev->BlockSize);
    if (!Buf) return EFI_OUT_OF_RESOURCES;

    EFI_STATUS st = blk_read(dev, 0, 1, Buf);
    if (EFI_ERROR(st)) { kfree(Buf); return st; }

    if (*(UINT16*)(Buf + 510) != MBR_SIGNATURE) { kfree(Buf); return EFI_NOT_FOUND; }

    struct MBRPart *Parts = (struct MBRPart*)(Buf + 446);
    // Verify at least one non-zero partition entry exists
    BOOLEAN has_part = FALSE;
    for (SSINT32 i = 0; i < 4; i++) {
        if (Parts[i].Type != 0x00 && Parts[i].Type != 0xEE) {
            has_part = TRUE;
            break;
        }
    }
    if (!has_part) { kfree(Buf); return EFI_NOT_FOUND; }

    for (SSINT32 i = 0; i < 4; i++) {
        UINT8 t = Parts[i].Type;
        if (t == 0x00 || t == 0xEE) continue;
        if (t == MBR_TYPE_FAT12 || t == MBR_TYPE_FAT16 ||
            t == MBR_TYPE_FAT16B || t == MBR_TYPE_FAT32 ||
            t == MBR_TYPE_FAT32LBA || t == MBR_TYPE_FAT16LBA ||
            t == MBR_TYPE_ESP) {
            *StartLBA = Parts[i].LBABegin;
            kfree(Buf);
            return EFI_SUCCESS;
        }
    }

    // Fallback: first non-empty partition
    for (SSINT32 i = 0; i < 4; i++) {
        if (Parts[i].Type != 0x00) {
            *StartLBA = Parts[i].LBABegin;
            kfree(Buf);
            return EFI_SUCCESS;
        }
    }

    kfree(Buf);
    return EFI_NOT_FOUND;
}

// ---- FAT driver ----

struct fat_fs {
    struct block_dev *Dev;
    UINT64  PartLBA;
    UINT16  BytsPerSec;
    UINT8   SecPerClus;
    UINT16  RsvdSecCnt;
    UINT8   NumFATs;
    UINT32  FATSz;
    UINT32  RootClus;
    UINT32  TotClus;
    UINT16  RootEntCnt;
    BOOLEAN IsFAT32;
    UINT32  ClusSize;

    void   *FatBuf;   // cached FAT
    UINT32  FATEntries;
    UINT32 *FAT32;
    UINT16 *FAT16;
};

static UINT64 clus_to_lba(struct fat_fs *fs, UINT32 Clus) {
    UINT32 RootSecs = fs->IsFAT32 ? 0
        : ((fs->RootEntCnt * 32) + fs->BytsPerSec - 1) / fs->BytsPerSec;
    UINT32 FirstDataSec = fs->RsvdSecCnt + fs->NumFATs * fs->FATSz + RootSecs;
    return fs->PartLBA + FirstDataSec + (UINT64)(Clus - 2) * fs->SecPerClus;
}

static UINT32 fat_next(struct fat_fs *fs, UINT32 Clus) {
    if (fs->IsFAT32) {
        if (Clus >= fs->FATEntries) return 0x0FFFFFF8;
        UINT32 v = fs->FAT32[Clus] & 0x0FFFFFFF;
        if (v == 0x00000000) return 0;
        if (v >= 0x0FFFFFF8) return 0x0FFFFFF8;
        return v;
    } else {
        if (Clus >= fs->FATEntries) return 0xFFF8;
        UINT16 v = fs->FAT16[Clus];
        if (v == 0x0000) return 0;
        if (v >= 0xFFF8) return 0x0FFFFFF8;
        return v;
    }
}

static EFI_STATUS fat_init(struct fat_fs *fs, struct block_dev *dev, UINT64 PartLBA) {
    fs->Dev = dev;
    fs->PartLBA = PartLBA;

    UINT8 *Buf = (UINT8*)kmalloc(dev->BlockSize);
    if (!Buf) return EFI_OUT_OF_RESOURCES;

    EFI_STATUS st = blk_read(dev, PartLBA, 1, Buf);
    if (EFI_ERROR(st)) { kfree(Buf); return st; }

    if (*(UINT16*)(Buf + 510) != MBR_SIGNATURE) { kfree(Buf); return EFI_UNSUPPORTED; }

    struct BPB *bpb = (struct BPB*)Buf;
    fs->BytsPerSec = bpb->BytsPerSec;
    fs->SecPerClus = bpb->SecPerClus;
    fs->RsvdSecCnt = bpb->RsvdSecCnt;
    fs->NumFATs = bpb->NumFATs;
    fs->RootEntCnt = bpb->RootEntCnt;

    UINT32 TotSec = bpb->TotSec16 ? bpb->TotSec16 : bpb->TotSec32;
    UINT32 RootSecs = ((fs->RootEntCnt * 32) + fs->BytsPerSec - 1) / fs->BytsPerSec;
    UINT32 FATSz = bpb->FATSz16 ? bpb->FATSz16 : bpb->FATSz32;
    fs->FATSz = FATSz;

    UINT32 FirstDataSec = fs->RsvdSecCnt + fs->NumFATs * FATSz + RootSecs;
    fs->TotClus = (TotSec - FirstDataSec) / fs->SecPerClus;

    if (fs->TotClus < 4085) {
        serial_write("Sylva: FS: FAT12 unsupported\n");
        kfree(Buf);
        return EFI_UNSUPPORTED;
    } else if (fs->TotClus < 65525) {
        fs->IsFAT32 = FALSE;
        fs->FATEntries = FATSz * fs->BytsPerSec / 2;
    } else {
        fs->IsFAT32 = TRUE;
        fs->RootClus = bpb->RootClus;
        fs->FATEntries = FATSz * fs->BytsPerSec / 4;
    }

    fs->ClusSize = fs->BytsPerSec * fs->SecPerClus;

    // Cache FAT
    UINTN FATBytes = FATSz * fs->BytsPerSec;
    fs->FatBuf = kmalloc(FATBytes);
    if (!fs->FatBuf) { kfree(Buf); return EFI_OUT_OF_RESOURCES; }

    st = blk_read(dev, PartLBA + fs->RsvdSecCnt, FATSz, fs->FatBuf);
    if (EFI_ERROR(st)) { kfree(Buf); kfree(fs->FatBuf); return st; }

    if (fs->IsFAT32) {
        fs->FAT32 = (UINT32*)fs->FatBuf;
        fs->FAT16 = NULL;
    } else {
        fs->FAT16 = (UINT16*)fs->FatBuf;
        fs->FAT32 = NULL;
    }

    kfree(Buf);
    return EFI_SUCCESS;
}

// ---- LFN helpers ----

static UINT8 lfn_checksum(const UINT8 *SFN) {
    UINT8 sum = 0;
    for (SSINT32 i = 0; i < 11; i++)
        sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1) + SFN[i];
    return sum;
}

#define LFN_MAX_FRAGS 20
#define LFN_FRAG_SIZE 13

struct lfn_state {
    CHAR16 frags[LFN_MAX_FRAGS][LFN_FRAG_SIZE + 1];
    UINTN  count;
    UINT8  checksum;
};

static void lfn_reset(struct lfn_state *lfn) {
    lfn->count = 0;
    lfn->checksum = 0;
}

static void lfn_add(struct lfn_state *lfn, const UINT8 *E) {
    if (lfn->count >= LFN_MAX_FRAGS) return;
    lfn->checksum = E[13];
    UINTN pos = 0;
    BOOLEAN done = FALSE;
    for (SSINT32 i = 0; i < 5 && pos < LFN_FRAG_SIZE && !done; i++) {
        CHAR16 c = *(const UINT16*)(E + 1 + i * 2);
        if (c == 0x0000 || c == 0xFFFF) { done = TRUE; break; }
        lfn->frags[lfn->count][pos++] = c;
    }
    for (SSINT32 i = 0; i < 6 && pos < LFN_FRAG_SIZE && !done; i++) {
        CHAR16 c = *(const UINT16*)(E + 14 + i * 2);
        if (c == 0x0000 || c == 0xFFFF) { done = TRUE; break; }
        lfn->frags[lfn->count][pos++] = c;
    }
    for (SSINT32 i = 0; i < 2 && pos < LFN_FRAG_SIZE && !done; i++) {
        CHAR16 c = *(const UINT16*)(E + 28 + i * 2);
        if (c == 0x0000 || c == 0xFFFF) { done = TRUE; break; }
        lfn->frags[lfn->count][pos++] = c;
    }
    lfn->frags[lfn->count][pos] = 0;
    lfn->count++;
}

// Fragments arrive in reverse order (seq=N first, seq=1 last).
// Concatenate from last to first to get correct name order.
static void lfn_build(struct lfn_state *lfn, CHAR16 *out, UINTN out_size) {
    UINTN pos = 0;
    for (UINTN i = lfn->count; i > 0; i--) {
        for (UINTN j = 0; lfn->frags[i - 1][j] && pos < out_size - 1; j++)
            out[pos++] = lfn->frags[i - 1][j];
    }
    out[pos] = 0;
}

static void sfn_to_name(const UINT8 *E, CHAR16 *out, UINTN out_size) {
    UINTN pos = 0;
    for (SSINT32 i = 0; i < 8 && pos < out_size - 1; i++)
        if (E[i] != ' ') out[pos++] = E[i];
    UINTN ext_start = pos;
    BOOLEAN has_ext = FALSE;
    for (SSINT32 i = 8; i < 11 && pos < out_size - 1; i++) {
        if (E[i] != ' ') {
            if (!has_ext) { out[pos++] = '.'; has_ext = TRUE; ext_start = pos; }
            out[pos++] = E[i];
        }
    }
    // If extension is empty but we added a dot, remove it
    if (has_ext && pos == ext_start) pos--;
    out[pos] = 0;
}

// ---- Directory reading ----

typedef void (*dir_cb)(void *Ctx, const CHAR16 *Name, UINT8 Attr,
                        UINT32 Size, UINT32 FirstClus, UINTN EntryOff);

// Returns TRUE when end-of-directory reached
static BOOLEAN process_sector(struct fat_fs *fs, UINT8 *Buf, UINTN SectBase,
                                struct lfn_state *lfn,
                                dir_cb Callback, void *Ctx) {
    for (UINTN off = 0; off < fs->BytsPerSec; off += 32) {
        UINT8 *E = Buf + off;

        if (E[0] == 0x00) return TRUE;
        if (E[0] == 0xE5) { lfn_reset(lfn); continue; }

        UINT8 Attr = E[11];

        if ((Attr & 0x3F) == 0x0F) {
            if (E[0] & 0x40) lfn_reset(lfn);
            lfn_add(lfn, E);
            continue;
        }

        // SFN entry
        CHAR16 Name[256];
        BOOLEAN use_lfn = FALSE;
        if (lfn->count > 0 && lfn_checksum(E) == lfn->checksum) {
            lfn_build(lfn, Name, 256);
            use_lfn = TRUE;
        }
        if (!use_lfn) sfn_to_name(E, Name, 256);

        // Skip . and ..
        if (E[0] == 0x2E) { lfn_reset(lfn); continue; }

        UINT32 Size = *(const UINT32*)(E + 28);
        UINT32 FirstClus;
        if (fs->IsFAT32)
            FirstClus = ((UINT32)*(const UINT16*)(E + 20) << 16)
                       | *(const UINT16*)(E + 26);
        else
            FirstClus = *(const UINT16*)(E + 26);

        Callback(Ctx, Name, Attr, Size, FirstClus, SectBase + off);
        lfn_reset(lfn);
    }
    return FALSE;
}

static void read_directory(struct fat_fs *fs, UINT32 Cluster,
                            dir_cb Callback, void *Ctx) {
    UINTN ClusBytes = fs->ClusSize;
    UINT8 *Buf = (UINT8*)kmalloc(ClusBytes);
    if (!Buf) return;

    struct lfn_state lfn;
    lfn_reset(&lfn);

    if (fs->IsFAT32) {
        UINT32 Clus = Cluster;
        while (Clus >= 2 && Clus < 0x0FFFFFF8) {
            UINT64 BaseLBA = clus_to_lba(fs, Clus);
            if (EFI_ERROR(blk_read(fs->Dev, BaseLBA, fs->SecPerClus, Buf)))
                goto done;
            for (UINTN s = 0; s < fs->SecPerClus; s++) {
                if (process_sector(fs, Buf + s * fs->BytsPerSec,
                                    s * fs->BytsPerSec,
                                    &lfn, Callback, Ctx))
                    goto done;
            }
            Clus = fat_next(fs, Clus);
        }
    } else if (Cluster == 0) {
        UINT32 RootSecs = ((fs->RootEntCnt * 32) + fs->BytsPerSec - 1) / fs->BytsPerSec;
        UINT64 RootLBA = fs->PartLBA + fs->RsvdSecCnt + fs->NumFATs * fs->FATSz;
        for (UINTN s = 0; s < RootSecs; s++) {
            if (EFI_ERROR(blk_read(fs->Dev, RootLBA + s, 1, Buf + s * fs->BytsPerSec)))
                goto done;
        }
        for (UINTN s = 0; s < RootSecs; s++) {
            if (process_sector(fs, Buf + s * fs->BytsPerSec,
                                s * fs->BytsPerSec,
                                &lfn, Callback, Ctx))
                goto done;
        }
    } else {
        UINT32 Clus = Cluster;
        while (Clus >= 2 && Clus < 0x0FFFFFF8) {
            UINT64 BaseLBA = clus_to_lba(fs, Clus);
            if (EFI_ERROR(blk_read(fs->Dev, BaseLBA, fs->SecPerClus, Buf)))
                goto done;
            for (UINTN s = 0; s < fs->SecPerClus; s++) {
                if (process_sector(fs, Buf + s * fs->BytsPerSec,
                                    s * fs->BytsPerSec,
                                    &lfn, Callback, Ctx))
                    goto done;
            }
            Clus = fat_next(fs, Clus);
        }
    }

done:
    kfree(Buf);
}

// ---- Public API ----

static struct fat_fs g_fs;
static BOOLEAN g_fs_inited = FALSE;

// Recursive listing support

struct list_ctx {
    struct fat_fs *fs;
    SSINT32 depth;
};

static void name_to_ascii(const CHAR16 *Name, char *Ascii, UINTN ascii_sz) {
    wstr_to_ascii(Ascii, (WString)Name, ascii_sz);
}

static void list_callback(void *ctx, const CHAR16 *Name, UINT8 Attr,
                           UINT32 Size, UINT32 FirstClus, UINTN EntryOff) {
    struct list_ctx *lc = (struct list_ctx*)ctx;
    for (SSINT32 i = 0; i < lc->depth; i++) serial_write("  ");

    serial_write(Attr & 0x10 ? "[DIR]  " : "[FILE] ");

    char ascii[256];
    name_to_ascii(Name, ascii, sizeof(ascii));
    serial_write(ascii);

    if (!(Attr & 0x10)) {
        serial_write(" (");
        serial_write_hex(Size);
        serial_write(" bytes)");
    }
    serial_write("\n");

    if ((Attr & 0x10) && lc->depth < 8) {
        struct list_ctx sub = { lc->fs, lc->depth + 1 };
        read_directory(lc->fs, FirstClus, list_callback, &sub);
    }
}

EFI_STATUS fs_init() {
    serial_write("Sylva: FS: init block...\n");
    static struct block_dev dev;
    EFI_STATUS st = blk_init(&dev);
    if (EFI_ERROR(st)) {
        serial_write("Sylva: FS: no block device!\n");
        return st;
    }

    serial_write("Sylva: FS: block size = ");
    serial_write_hex(dev.BlockSize);
    serial_write("\n");

    UINT64 PartLBA = 0;
    st = find_gpt_partition(&dev, &PartLBA);
    if (EFI_ERROR(st)) {
        serial_write("Sylva: FS: no GPT, trying MBR...\n");
        st = find_mbr_partition(&dev, &PartLBA);
    }
    if (EFI_ERROR(st)) {
        serial_write("Sylva: FS: no partition table, assuming super-floppy...\n");
        PartLBA = 0;
    }

    serial_write("Sylva: FS: partition LBA = ");
    serial_write_hex(PartLBA);
    serial_write("\n");

    st = fat_init(&g_fs, &dev, PartLBA);
    if (EFI_ERROR(st)) {
        serial_write("Sylva: FS: FAT init failed!\n");
        return st;
    }

    serial_write("Sylva: FS: FAT");
    serial_write(g_fs.IsFAT32 ? "32" : "16");
    serial_write(" ready, cluster size = ");
    serial_write_hex(g_fs.ClusSize);
    serial_write("\n");

    g_fs_inited = TRUE;
    return EFI_SUCCESS;
}

void fs_list() {
    if (!g_fs_inited) { serial_write("FS not initialized\n"); return; }
    UINT32 Root = g_fs.IsFAT32 ? g_fs.RootClus : 0;
    struct list_ctx lc = { &g_fs, 0 };
    read_directory(&g_fs, Root, list_callback, &lc);
}

// ---- File reading ----

struct find_ctx {
    const CHAR16 *Target;
    BOOLEAN Found;
    UINT32 Cluster;
    UINT32 Size;
    UINT8  Attr;
};

static BOOLEAN name_match(const CHAR16 *a, const CHAR16 *b) {
    while (*a && *b) {
        CHAR16 ca = *a, cb = *b;
        if (ca >= L'A' && ca <= L'Z') ca += 32;
        if (cb >= L'A' && cb <= L'Z') cb += 32;
        if (ca != cb) return FALSE;
        a++; b++;
    }
    return *a == 0 && *b == 0;
}

static void find_callback(void *ctx, const CHAR16 *Name, UINT8 Attr,
                           UINT32 Size, UINT32 FirstClus, UINTN EntryOff) {
    struct find_ctx *fc = (struct find_ctx*)ctx;
    if (fc->Found) return;
    if (name_match(fc->Target, Name)) {
        fc->Found = TRUE;
        fc->Cluster = FirstClus;
        fc->Size = Size;
        fc->Attr = Attr;
    }
}

EFI_STATUS fs_read(WString Path, void **Buffer, UINTN *Size) {
    if (!g_fs_inited) return EFI_NOT_READY;

    const CHAR16 *p = Path;
    while (*p == L'\\') p++;

    UINT32 CurClus = g_fs.IsFAT32 ? g_fs.RootClus : 0;

    CHAR16 Comp[256];
    while (*p) {
        UINTN ci = 0;
        while (*p && *p != L'\\' && ci < 255) Comp[ci++] = *p++;
        Comp[ci] = 0;
        while (*p == L'\\') p++;

        struct find_ctx fc;
        fc.Target = Comp;
        fc.Found = FALSE;
        read_directory(&g_fs, CurClus, find_callback, &fc);
        if (!fc.Found) return EFI_NOT_FOUND;

        if (*p == 0 && !(fc.Attr & 0x10)) {
            UINT32 FileSz = fc.Size;
            void *Buf = kmalloc(FileSz ? FileSz : 1);
            if (!Buf) return EFI_OUT_OF_RESOURCES;

            if (FileSz > 0) {
                UINT32 Clus = fc.Cluster;
                UINTN Offset = 0;
                UINTN ClusBytes = g_fs.ClusSize;

                while (Clus >= 2 && Clus < 0x0FFFFFF8 && Offset < FileSz) {
                    // Find longest contiguous run starting at Clus
                    UINT32 RunStart = Clus;
                    UINT32 RunEnd = Clus;
                    UINT32 Next = fat_next(&g_fs, Clus);
                    while (Next == RunEnd + 1 && Next >= 2 && Next < 0x0FFFFFF8) {
                        RunEnd = Next;
                        Next = fat_next(&g_fs, RunEnd);
                    }
                    UINTN RunClus = (UINTN)(RunEnd - RunStart + 1);
                    UINT64 LBA = clus_to_lba(&g_fs, RunStart);
                    UINTN Want = RunClus * ClusBytes;
                    if (Want > FileSz - Offset) Want = FileSz - Offset;
                    UINTN NSecs = (Want + g_fs.BytsPerSec - 1) / g_fs.BytsPerSec;
                    EFI_STATUS st = blk_read(g_fs.Dev, LBA, NSecs,
                                              (UINT8*)Buf + Offset);
                    if (EFI_ERROR(st)) { kfree(Buf); return st; }
                    Offset += Want;
                    Clus = Next;
                }
            }

            *Buffer = Buf;
            *Size = FileSz;
            return EFI_SUCCESS;
        }

        if (!(fc.Attr & 0x10)) return EFI_NOT_FOUND;
        CurClus = fc.Cluster;
    }

    return EFI_NOT_FOUND;
}

// ---- FAT allocation ----

static BOOLEAN fat_entry_free(struct fat_fs *fs, UINT32 Clus) {
    if (Clus >= fs->FATEntries) return FALSE;
    if (fs->IsFAT32) return (fs->FAT32[Clus] & 0x0FFFFFFF) == 0;
    return fs->FAT16[Clus] == 0;
}

static void fat_entry_set(struct fat_fs *fs, UINT32 Clus, UINT32 Val) {
    if (Clus >= fs->FATEntries) return;
    if (fs->IsFAT32) fs->FAT32[Clus] = Val;
    else fs->FAT16[Clus] = (UINT16)Val;
}

// Allocate `Count` free clusters and link them into a chain.
// Returns the first cluster of the new chain; the last entry is marked EOF.
static EFI_STATUS fat_alloc_chain(struct fat_fs *fs, UINT32 Count, UINT32 *OutFirst) {
    UINT32 Found = 0;
    UINT32 First = 0, Prev = 0;
    UINT32 EOC = fs->IsFAT32 ? 0x0FFFFFFF : 0xFFFF;

    for (UINT32 i = 2; i < fs->FATEntries && Found < Count; i++) {
        if (!fat_entry_free(fs, i)) continue;
        if (Found == 0) First = i;
        else fat_entry_set(fs, Prev, i);
        Found++;
        Prev = i;
    }
    if (Found < Count) return EFI_OUT_OF_RESOURCES;
    fat_entry_set(fs, Prev, EOC);
    *OutFirst = First;
    return EFI_SUCCESS;
}

// Write the in-memory FAT cache back to all FAT copies on disk.
static EFI_STATUS fat_flush(struct fat_fs *fs) {
    for (UINTN f = 0; f < fs->NumFATs; f++) {
        EFI_STATUS st = blk_write(fs->Dev,
            fs->PartLBA + fs->RsvdSecCnt + f * fs->FATSz,
            fs->FATSz, fs->FatBuf);
        if (EFI_ERROR(st)) return st;
    }
    return EFI_SUCCESS;
}

// ---- File writing ----

struct find_write_ctx {
    const CHAR16 *Target;
    BOOLEAN Found;
    UINT32 Cluster;
    UINT32 Size;
    UINT8  Attr;
    UINTN  DirOffset;
};

static void find_write_callback(void *ctx, const CHAR16 *Name, UINT8 Attr,
                                 UINT32 Size, UINT32 FirstClus, UINTN EntryOff) {
    struct find_write_ctx *fc = (struct find_write_ctx*)ctx;
    if (fc->Found) return;
    if (name_match(fc->Target, Name)) {
        fc->Found = TRUE;
        fc->Cluster = FirstClus;
        fc->Size = Size;
        fc->Attr = Attr;
        fc->DirOffset = EntryOff;
    }
}

// Update a dirent's size + first-cluster fields in-place inside `DirBuf`
// at the given byte offset. For FAT32 we also patch the high cluster word.
static void dirent_update_size(void *DirBuf, UINTN EntryOff,
                                UINT32 NewSize, UINT32 FirstClus,
                                BOOLEAN PatchFirstClus, BOOLEAN IsFAT32) {
    UINT8 *E = (UINT8*)DirBuf + EntryOff;
    *(UINT32*)(E + 28) = NewSize;
    if (PatchFirstClus) {
        if (IsFAT32) {
            *(UINT16*)(E + 20) = (UINT16)(FirstClus >> 16);
            *(UINT16*)(E + 26) = (UINT16)(FirstClus & 0xFFFF);
        } else {
            *(UINT16*)(E + 26) = (UINT16)(FirstClus & 0xFFFF);
        }
    }
}

// Write [Data, Data+Size) into the file at Path, starting at byte Offset.
// Semantics: existing file only (no create); Offset must be <= current size.
// New file size = Offset + Size. Trailing clusters beyond the new size are
// left allocated (lost) — no truncate support.
EFI_STATUS fs_write(WString Path, const void *Data, UINTN Size, UINTN Offset) {
    if (!g_fs_inited) return EFI_NOT_READY;
    if (Data == NULL && Size > 0) return EFI_INVALID_PARAMETER;

    const CHAR16 *p = Path;
    while (*p == L'\\') p++;

    UINT32 CurClus = g_fs.IsFAT32 ? g_fs.RootClus : 0;

    CHAR16 Comp[256];
    BOOLEAN Resolved = FALSE;
    UINT32 FileFirst = 0, FileSize = 0, FileAttr = 0;
    UINTN  FileDirOff = 0;

    while (*p) {
        UINTN ci = 0;
        while (*p && *p != L'\\' && ci < 255) Comp[ci++] = *p++;
        Comp[ci] = 0;
        while (*p == L'\\') p++;

        struct find_write_ctx fc;
        fc.Target = Comp;
        fc.Found = FALSE;
        read_directory(&g_fs, CurClus, find_write_callback, &fc);
        if (!fc.Found) return EFI_NOT_FOUND;

        if (*p == 0) {
            if (fc.Attr & 0x10) return EFI_INVALID_PARAMETER;
            Resolved = TRUE;
            FileFirst = fc.Cluster;
            FileSize = fc.Size;
            FileAttr = fc.Attr;
            FileDirOff = fc.DirOffset;
        } else {
            if (!(fc.Attr & 0x10)) return EFI_NOT_FOUND;
            CurClus = fc.Cluster;
        }
    }
    if (!Resolved) return EFI_NOT_FOUND;
    if (Offset > FileSize) return EFI_INVALID_PARAMETER;

    UINT32 NewSize = (UINT32)(Offset + Size);
    UINT32 OldClusCount = (FileSize + g_fs.ClusSize - 1) / g_fs.ClusSize;
    UINT32 NewClusCount = (NewSize + g_fs.ClusSize - 1) / g_fs.ClusSize;
    UINT32 AddClusCount = NewClusCount - OldClusCount;
    BOOLEAN FileWasEmpty = (FileSize == 0);

    // --- Extend cluster chain if we need more clusters ---
    if (AddClusCount > 0) {
        UINT32 NewFirst;
        EFI_STATUS st = fat_alloc_chain(&g_fs, AddClusCount, &NewFirst);
        if (EFI_ERROR(st)) return st;

        if (FileWasEmpty) {
            FileFirst = NewFirst;
        } else {
            // Walk to the last cluster of the existing chain
            UINT32 Cur = FileFirst;
            UINT32 Next;
            while (TRUE) {
                Next = fat_next(&g_fs, Cur);
                if (Next >= 0x0FFFFFF8) break;
                Cur = Next;
            }
            // Cur is the last cluster; link to new chain
            fat_entry_set(&g_fs, Cur, NewFirst);
        }
    }

    // --- Write data clusters ---
    if (Size > 0) {
        // Walk to the cluster containing byte Offset
        UINT32 Clus = FileFirst;
        UINTN SkipBytes = Offset;
        while (SkipBytes >= g_fs.ClusSize) {
            Clus = fat_next(&g_fs, Clus);
            SkipBytes -= g_fs.ClusSize;
        }

        void *ClusBuf = kmalloc(g_fs.ClusSize);
        if (!ClusBuf) return EFI_OUT_OF_RESOURCES;

        const UINT8 *Src = (const UINT8*)Data;
        UINTN Remaining = Size;
        UINTN InClusOff = SkipBytes;

        while (Remaining > 0) {
            UINT64 LBA = clus_to_lba(&g_fs, Clus);
            UINTN SpaceInClus = g_fs.ClusSize - InClusOff;
            UINTN Chunk = Remaining < SpaceInClus ? Remaining : SpaceInClus;

            if (InClusOff > 0 || Chunk < g_fs.ClusSize) {
                EFI_STATUS rst = blk_read(g_fs.Dev, LBA, g_fs.SecPerClus, ClusBuf);
                if (EFI_ERROR(rst)) { kfree(ClusBuf); return rst; }
                mem_copy((UINT8*)ClusBuf + InClusOff, Src, Chunk);
                EFI_STATUS wst = blk_write(g_fs.Dev, LBA, g_fs.SecPerClus, ClusBuf);
                if (EFI_ERROR(wst)) { kfree(ClusBuf); return wst; }
            } else {
                EFI_STATUS wst = blk_write(g_fs.Dev, LBA, g_fs.SecPerClus, Src);
                if (EFI_ERROR(wst)) { kfree(ClusBuf); return wst; }
            }

            Src += Chunk;
            Remaining -= Chunk;
            InClusOff = 0;
            if (Remaining > 0) {
                Clus = fat_next(&g_fs, Clus);
            }
        }

        kfree(ClusBuf);
    }

    // --- Update dirent (size, and first cluster if file was empty) ---
    {
        void *DirBuf;
        UINT64 DirLBA;
        UINTN  DirNSecs;
        UINTN  DirBufBytes;

        if (g_fs.IsFAT32) {
            DirLBA = clus_to_lba(&g_fs, CurClus);
            DirNSecs = g_fs.SecPerClus;
            DirBufBytes = g_fs.ClusSize;
        } else if (CurClus == 0) {
            UINT32 RootSecs = ((g_fs.RootEntCnt * 32) + g_fs.BytsPerSec - 1) / g_fs.BytsPerSec;
            DirLBA = g_fs.PartLBA + g_fs.RsvdSecCnt + g_fs.NumFATs * g_fs.FATSz;
            DirNSecs = RootSecs;
            DirBufBytes = RootSecs * g_fs.BytsPerSec;
        } else {
            DirLBA = clus_to_lba(&g_fs, CurClus);
            DirNSecs = g_fs.SecPerClus;
            DirBufBytes = g_fs.ClusSize;
        }

        DirBuf = kmalloc(DirBufBytes);
        if (!DirBuf) return EFI_OUT_OF_RESOURCES;

        EFI_STATUS rst = blk_read(g_fs.Dev, DirLBA, DirNSecs, DirBuf);
        if (EFI_ERROR(rst)) { kfree(DirBuf); return rst; }

        dirent_update_size(DirBuf, FileDirOff, NewSize, FileFirst,
                            FileWasEmpty, g_fs.IsFAT32);

        EFI_STATUS wst = blk_write(g_fs.Dev, DirLBA, DirNSecs, DirBuf);
        kfree(DirBuf);
        if (EFI_ERROR(wst)) return wst;
    }

    // --- Flush FAT ---
    return fat_flush(&g_fs);
}
