import fs from "node:fs";
import path from "node:path";
import {
  DATA_ROOT,
  createApplyRun,
  getApprovedAssets,
  listMechanics,
  listPublishableCharacters,
  listStages,
} from "./db.js";
import {
  SPRITE_MOVE_TARGETS,
  latestSpriteKeyframeMedia,
  spriteKeyframeDestination,
} from "./sprite-pipeline.js";

const DEFAULT_GODOT_ROOT = process.env.SSB_GODOT_ROOT || "/Users/jason/Projects/anime-fighter-pro";

function ensureDir(targetPath) {
  fs.mkdirSync(targetPath, { recursive: true });
}

function writeJson(filePath, payload) {
  ensureDir(path.dirname(filePath));
  fs.writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n");
}

function loadJson(filePath) {
  if (!fs.existsSync(filePath)) {
    return {};
  }
  try {
    return JSON.parse(fs.readFileSync(filePath, "utf8"));
  } catch (_error) {
    return {};
  }
}

function toResPath(godotRoot, absolutePath) {
  const relative = path.relative(godotRoot, absolutePath).split(path.sep).join("/");
  return `res://${relative}`;
}

function fromResPath(godotRoot, resPath) {
  const relative = String(resPath || "").replace(/^res:\/\//, "");
  return path.join(godotRoot, relative);
}

function destinationForAsset(asset, godotRoot) {
  const ext = path.extname(asset.original_filename || asset.stored_filename || "");
  const slug = asset.entity_slug || "shared";
  const safeSlot = String(asset.slot || "misc").trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_");

  if (asset.entity_type === "character") {
    const map = {
      spritesheet: ["assets", "content", "characters", slug, "sprites", `main${ext}`],
      portrait: ["assets", "content", "characters", slug, "ui", `portrait${ext}`],
      select_art: ["assets", "content", "characters", slug, "ui", `select${ext}`],
      unlock_art: ["assets", "content", "characters", slug, "ui", `unlock${ext}`],
      reference_sheet: ["assets", "content", "characters", slug, "reference", `sheet${ext}`],
      gallery_support: ["assets", "content", "characters", slug, "gallery", `support${ext}`],
      stock_icon: ["assets", "content", "characters", slug, "ui", `stock${ext}`],
      model: ["assets", "content", "characters", slug, "models", `fighter${ext}`],
      voice_bank: ["assets", "content", "characters", slug, "audio", `voice${ext}`],
      theme_music: ["assets", "content", "characters", slug, "audio", `theme${ext}`],
    };
    const pieces = map[safeSlot] || ["assets", "content", "characters", slug, "misc", `${safeSlot}${ext}`];
    return path.join(godotRoot, ...pieces);
  }

  if (asset.entity_type === "stage") {
    const map = {
      preview_art: ["assets", "content", "stages", slug, "art", `preview${ext}`],
      background_art: ["assets", "content", "stages", slug, "art", `background${ext}`],
      platform_art: ["assets", "content", "stages", slug, "art", `platforms${ext}`],
      geometry_ref: ["assets", "content", "stages", slug, "geometry", `layout${ext}`],
      music: ["assets", "content", "stages", slug, "audio", `theme${ext}`],
    };
    const pieces = map[safeSlot] || ["assets", "content", "stages", slug, "misc", `${safeSlot}${ext}`];
    return path.join(godotRoot, ...pieces);
  }

  return path.join(godotRoot, "assets", "content", "shared", safeSlot, `${slug}${ext}`);
}

function latestApprovedAssetMap(assets) {
  const map = new Map();
  for (const asset of assets) {
    const key = `${asset.entity_type}:${asset.entity_slug}:${asset.slot}`;
    if (!map.has(key)) {
      map.set(key, asset);
    }
  }
  return map;
}

export function runApplyToGodot(options = {}) {
  const godotRoot = path.resolve(options.godotRoot || DEFAULT_GODOT_ROOT);
  const generatedDataRoot = path.join(godotRoot, "assets", "data", "generated");
  const charactersDataRoot = path.join(generatedDataRoot, "characters");
  const stagesDataRoot = path.join(generatedDataRoot, "stages");
  const contentRoot = path.join(godotRoot, "assets", "content");
  const applyReportPath = path.join(generatedDataRoot, "apply-report.json");

  ensureDir(generatedDataRoot);
  ensureDir(charactersDataRoot);
  ensureDir(stagesDataRoot);
  ensureDir(contentRoot);

  const characters = listPublishableCharacters();
  const stages = listStages();
  const mechanics = listMechanics();
  const approvedAssets = getApprovedAssets();
  const assetMap = latestApprovedAssetMap(approvedAssets);
  const keyframeMediaMap = latestSpriteKeyframeMedia();
  const previousReport = loadJson(applyReportPath);
  const desiredDestinations = new Set();

  const copyLog = [];
  const characterRecords = [];
  const stageRecords = [];

  for (const asset of assetMap.values()) {
    desiredDestinations.add(toResPath(godotRoot, destinationForAsset(asset, godotRoot)));
  }
  for (const media of keyframeMediaMap.values()) {
    const sourcePath = path.join(DATA_ROOT, media.relative_upload_path);
    if (!fs.existsSync(sourcePath)) {
      continue;
    }
    desiredDestinations.add(toResPath(godotRoot, spriteKeyframeDestination(godotRoot, media.character_slug, media.section_key, path.extname(sourcePath))));
  }

  for (const entry of previousReport.log || []) {
    if (entry.status !== "copied" || !entry.destination || desiredDestinations.has(entry.destination)) {
      continue;
    }
    const stalePath = fromResPath(godotRoot, entry.destination);
    if (fs.existsSync(stalePath)) {
      fs.rmSync(stalePath, { force: true });
      copyLog.push({
        status: "removed-stale",
        destination: entry.destination,
      });
    }
  }

  for (const asset of assetMap.values()) {
    const sourcePath = path.join(DATA_ROOT, asset.relative_upload_path);
    if (!fs.existsSync(sourcePath)) {
      copyLog.push({
        status: "missing-source",
        entity_type: asset.entity_type,
        entity_slug: asset.entity_slug,
        slot: asset.slot,
        sourcePath,
      });
      continue;
    }

    const destinationPath = destinationForAsset(asset, godotRoot);
    ensureDir(path.dirname(destinationPath));
    fs.copyFileSync(sourcePath, destinationPath);
    copyLog.push({
      status: "copied",
      entity_type: asset.entity_type,
      entity_slug: asset.entity_slug,
      slot: asset.slot,
      destination: toResPath(godotRoot, destinationPath),
    });
  }

  for (const media of keyframeMediaMap.values()) {
    const sourcePath = path.join(DATA_ROOT, media.relative_upload_path);
    if (!fs.existsSync(sourcePath)) {
      copyLog.push({
        status: "missing-source",
        entity_type: "moveset_media",
        entity_slug: media.character_slug,
        slot: media.section_key,
        sourcePath,
      });
      continue;
    }

    const destinationPath = spriteKeyframeDestination(godotRoot, media.character_slug, media.section_key, path.extname(sourcePath));
    ensureDir(path.dirname(destinationPath));
    fs.copyFileSync(sourcePath, destinationPath);
    copyLog.push({
      status: "copied",
      entity_type: "moveset_media",
      entity_slug: media.character_slug,
      slot: media.section_key,
      destination: toResPath(godotRoot, destinationPath),
    });
  }

  for (const character of characters) {
    const assets = {};
    const attack_keyframes = {};
    for (const asset of approvedAssets) {
      if (asset.entity_type !== "character" || asset.entity_slug !== character.slug) {
        continue;
      }
      const destinationPath = destinationForAsset(asset, godotRoot);
      if (fs.existsSync(destinationPath)) {
        assets[asset.slot] = {
          label: asset.label,
          mime_type: asset.mime_type,
          notes: asset.notes,
          path: toResPath(godotRoot, destinationPath),
        };
      }
    }
    for (const target of SPRITE_MOVE_TARGETS) {
      const media = keyframeMediaMap.get(`${character.slug}:${target.section_key}`);
      if (!media) {
        continue;
      }
      const sourcePath = path.join(DATA_ROOT, media.relative_upload_path);
      if (!fs.existsSync(sourcePath)) {
        continue;
      }
      const destinationPath = spriteKeyframeDestination(godotRoot, character.slug, target.section_key, path.extname(sourcePath));
      if (!fs.existsSync(destinationPath)) {
        continue;
      }
      attack_keyframes[target.section_key] = {
        label: target.label,
        section_type: target.section_type,
        path: toResPath(godotRoot, destinationPath),
      };
    }

    const record = {
      slug: character.slug,
      display_name: character.display_name,
      roster_group: character.roster_group,
      release_bucket: character.release_bucket || "unassigned",
      archetype: character.archetype,
      franchise: character.franchise,
      tags: character.tags,
      status: character.status,
      description: character.description,
      mechanics_notes: character.mechanics_notes,
      moveset_notes: character.moveset_notes,
      unlock_notes: character.unlock_notes,
      is_public: Boolean(character.is_public),
      sort_order: character.sort_order,
      assets,
      attack_keyframes,
    };
    characterRecords.push(record);
    writeJson(path.join(charactersDataRoot, `${character.slug}.json`), record);
  }

  for (const stage of stages) {
    const assets = {};
    for (const asset of approvedAssets) {
      if (asset.entity_type !== "stage" || asset.entity_slug !== stage.slug) {
        continue;
      }
      const destinationPath = destinationForAsset(asset, godotRoot);
      if (fs.existsSync(destinationPath)) {
        assets[asset.slot] = {
          label: asset.label,
          mime_type: asset.mime_type,
          notes: asset.notes,
          path: toResPath(godotRoot, destinationPath),
        };
      }
    }

    const record = {
      slug: stage.slug,
      display_name: stage.display_name,
      category: stage.category,
      legal_status: stage.legal_status,
      tags: stage.tags,
      description: stage.description,
      size_notes: stage.size_notes,
      gimmick_notes: stage.gimmick_notes,
      music_notes: stage.music_notes,
      is_public: Boolean(stage.is_public),
      sort_order: stage.sort_order,
      assets,
    };
    stageRecords.push(record);
    writeJson(path.join(stagesDataRoot, `${stage.slug}.json`), record);
  }

  writeJson(path.join(generatedDataRoot, "characters.json"), {
    generated_at: new Date().toISOString(),
    count: characterRecords.length,
    characters: characterRecords,
  });

  writeJson(path.join(generatedDataRoot, "stages.json"), {
    generated_at: new Date().toISOString(),
    count: stageRecords.length,
    stages: stageRecords,
  });

  writeJson(path.join(generatedDataRoot, "mechanics.json"), {
    generated_at: new Date().toISOString(),
    count: mechanics.length,
    mechanics: mechanics.map((mechanic) => ({
      slug: mechanic.slug,
      display_name: mechanic.display_name,
      subsystem: mechanic.subsystem,
      tuning_value: mechanic.tuning_value,
      target_file: mechanic.target_file,
      summary: mechanic.summary,
      notes: mechanic.notes,
      is_public: Boolean(mechanic.is_public),
      sort_order: mechanic.sort_order,
    })),
  });

  const summary = {
    generated_at: new Date().toISOString(),
    godot_root: godotRoot,
    copied_assets: copyLog.filter((entry) => entry.status === "copied").length,
    missing_assets: copyLog.filter((entry) => entry.status === "missing-source").length,
    removed_stale_assets: copyLog.filter((entry) => entry.status === "removed-stale").length,
    characters: characterRecords.length,
    stages: stageRecords.length,
    mechanics: mechanics.length,
    log: copyLog,
  };

  writeJson(applyReportPath, summary);
  const applyRunId = createApplyRun(summary);

  return {
    apply_run_id: applyRunId,
    summary,
  };
}
