import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const ROOT = dirname(fileURLToPath(import.meta.url)); // tests/fixtures
const p = (rel: string): string => join(ROOT, rel);

export const fixtureRoot = ROOT;
export const fixtureDir = {
  // Backward-compat flat aliases (used by format-matrix, hostile-inputs, multimodal tests)
  formats: join(ROOT, "image/formats"),
  hostile: join(ROOT, "image/hostile"),
  media: join(ROOT, "video/formats"),
  documents: join(ROOT, "document/formats"),
  data: join(ROOT, "data/valid"),
  security: join(ROOT, "security"),
  // Modality-structured dirs
  image: {
    valid: join(ROOT, "image/valid"),
    formats: join(ROOT, "image/formats"),
    edge: join(ROOT, "image/edge"),
    hostile: join(ROOT, "image/hostile"),
  },
  video: {
    valid: join(ROOT, "video/valid"),
    formats: join(ROOT, "video/formats"),
    hostile: join(ROOT, "video/hostile"),
  },
  audio: {
    valid: join(ROOT, "audio/valid"),
    formats: join(ROOT, "audio/formats"),
    hostile: join(ROOT, "audio/hostile"),
  },
  document: {
    valid: join(ROOT, "document/valid"),
    formats: join(ROOT, "document/formats"),
    edge: join(ROOT, "document/edge"),
    hostile: join(ROOT, "document/hostile"),
  },
};

export const fixtures = {
  image: {
    base: {
      png200: p("image/valid/test-200x150.png"),
      jpg100: p("image/valid/test-100x100.jpg"),
      webp50: p("image/valid/test-50x50.webp"),
      svg100: p("image/valid/test-100x100.svg"),
      heic200: p("image/valid/test-200x150.heic"),
    },
    scene: p("image/valid/test-scene.png"),
    portrait: {
      jpg: p("image/valid/portrait-color.jpg"),
      heic: p("image/valid/portrait-headshot.heic"),
      bw: p("image/valid/portrait-bw.jpeg"),
      isolated: p("image/valid/portrait-isolated.png"),
    },
    multiFace: p("image/valid/multi-face.webp"),
    redEye: p("image/valid/red-eye.jpg"),
    exifGps: p("image/valid/test-with-exif.jpg"),
    transparent: p("image/edge/test-fake-transparency.png"),
    animated: {
      gif: p("image/valid/animated.gif"),
      webp: p("image/valid/animated.webp"),
      real: p("image/valid/animated-simpsons.gif"),
    },
    ocr: {
      clean: p("image/valid/ocr-clean.png"),
      japanese: p("image/valid/ocr-japanese.png"),
      chat: p("image/valid/ocr-chat.jpeg"),
    },
    code: {
      barcodePng: p("image/valid/barcode.png"),
      barcodeAvif: p("image/valid/barcode.avif"),
      qrPng: p("image/valid/qr-code.png"),
      qrSvg: p("image/valid/qr-code.svg"),
      qrAvif: p("image/valid/qr-code.avif"),
    },
    svgLogo: p("image/valid/svg-logo.svg"),
    motorcycle: p("image/valid/motorcycle.heif"),
    crossFormatChat: p("image/valid/cross-format-chat.webp"),
    portraitJpg: p("image/valid/test-portrait.jpg"),
    portraitHeic: p("image/valid/test-portrait.heic"),
    watermark: p("image/valid/watermark.jpg"),
    stressLarge: p("image/valid/stress-large.jpg"),
    edge: {
      px1: p("image/edge/test-1x1.png"),
      blank: p("image/edge/test-blank.png"),
      tall: p("image/edge/test-portrait-tall.png"),
      extreme: p("image/edge/test-portrait-extreme.png"),
    },
    formats: (ext: string) => p(`image/formats/sample.${ext}`),
    multipageTiff: p("image/formats/multipage.tiff"),
    hostile: {
      truncated: p("image/hostile/truncated.jpg"),
      garbage: p("image/hostile/garbage.jpg"),
      bomb: p("image/hostile/bomb-50000x50000.png"),
      extMismatch: p("image/hostile/png-bytes.jpg"),
    },
    hostileEmpty: { zeroByte: p("image/hostile/zero-byte.png") },
  },
  video: {
    hero: {
      mp4: p("video/valid/media-30s.mp4"),
      mov: p("video/valid/hero.mov"),
      webm: p("video/valid/hero.webm"),
      mkv: p("video/valid/hero.mkv"),
      avi: p("video/valid/hero.avi"),
    },
    speech: { mp4: p("video/valid/speech-10s.mp4") },
    withMeta: p("video/valid/video-with-meta.mp4"),
    tiny: (ext: string) => p(`video/formats/tiny.${ext}`),
    subs: {
      mkv: p("video/formats/tiny-subs.mkv"),
      srt: p("video/formats/tiny.srt"),
      vtt: p("video/formats/tiny.vtt"),
      ass: p("video/formats/tiny.ass"),
    },
    hostile: { truncated: p("video/hostile/truncated.mp4") },
  },
  audio: {
    speech: {
      wav: p("audio/valid/speech-10s.wav"),
      flac: p("audio/valid/speech.flac"),
      ogg: p("audio/valid/speech.ogg"),
      m4a: p("audio/valid/speech.m4a"),
      aac: p("audio/valid/speech.aac"),
      opus: p("audio/valid/speech.opus"),
    },
    music: { wav: p("audio/valid/media-30s.wav") },
    tagged: p("audio/valid/audio-with-tags.mp3"),
    stereo: p("audio/formats/tone-stereo.wav"),
    gap: p("audio/formats/tone-gap.wav"),
    tiny: (ext: string) => p(`audio/formats/tiny.${ext}`),
    hostileEmpty: { zeroByte: p("audio/hostile/zero-byte.wav") },
  },
  document: {
    pdfMulti: p("document/valid/multipage-6.pdf"),
    pdf2: p("document/valid/alt-2page.pdf"),
    pdf3: p("document/valid/test-3page.pdf"),
    pdfScanned: p("document/valid/ocr-scanned.pdf"),
    encrypted: p("document/valid/encrypted.pdf"),
    tiny: (ext: string) => p(`document/formats/tiny.${ext}`),
    remoteImgHtml: p("document/edge/remote-img.html"),
    hostile: {
      truncatedDocx: p("document/hostile/truncated.docx"),
      garbagePdf: p("document/hostile/garbage.pdf"),
    },
  },
  data: {
    csv: p("data/valid/tiny.csv"),
    csvA: p("data/valid/tiny-a.csv"),
    csvB: p("data/valid/tiny-b.csv"),
    json: p("data/valid/tiny.json"),
    xml: p("data/valid/tiny.xml"),
    yaml: p("data/valid/tiny.yaml"),
    tsv: p("data/valid/tiny.tsv"),
    zip: p("data/valid/tiny.zip"),
  },
  security: {
    svgXxeFile: p("security/svg-xxe-file-read.svg"),
    svgXxeSsrf: p("security/svg-xxe-ssrf.svg"),
    polyglot: p("image/hostile/png-bytes.jpg"),
    htmlSsrf: p("document/edge/remote-img.html"),
  },
} as const;

export function readFixture(path: string): Buffer {
  return readFileSync(path);
}

// Recursively collect every string-valued leaf, EXCLUDING `*hostileEmpty*` groups
// (intentionally zero-byte) and function leaves (the `formats`/`tiny` accessors).
export function flattenFixturePaths(node: unknown): string[] {
  if (typeof node === "string") return [node];
  if (node && typeof node === "object") {
    return Object.entries(node).flatMap(([k, v]) =>
      k.toLowerCase().includes("hostileempty") ? [] : flattenFixturePaths(v),
    );
  }
  return []; // functions (accessors) and other non-leaves
}
