import {
  MACHINES,
  RECIPE_BY_ID,
  machinesForRecipe,
  preferredChainRecipeForProduct,
} from "./factorio-data.ts";
import type {
  ChainMasterSettings,
  Machine,
  ProductOption,
  Recipe,
} from "./types.ts";

export type MachinePreferenceGroup =
  | "crafting"
  | "smelting"
  | "fluids"
  | "biology"
  | "processing";

export const CHAIN_MACHINE_GROUPS: Array<{
  id: MachinePreferenceGroup;
  machineIds: string[];
}> = [
  {
    id: "crafting",
    machineIds: [
      "electromagnetic-plant",
      "assembling-machine-3",
      "assembling-machine-2",
      "assembling-machine-1",
    ],
  },
  {
    id: "smelting",
    machineIds: [
      "foundry",
      "electric-furnace",
      "steel-furnace",
      "stone-furnace",
    ],
  },
  {
    id: "fluids",
    machineIds: ["cryogenic-plant", "chemical-plant", "oil-refinery"],
  },
  {
    id: "biology",
    machineIds: ["biochamber", "captive-biter-spawner"],
  },
  {
    id: "processing",
    machineIds: [
      "centrifuge",
      "crusher",
      "recycler",
      "rocket-silo",
    ],
  },
];

export const DEFAULT_UNLOCKED_MACHINE_IDS = MACHINES.filter(
  (machine) => machine.energyKind !== "passive",
).map((machine) => machine.id);

export const DEFAULT_MACHINE_PRIORITIES: Record<
  MachinePreferenceGroup,
  string[]
> = Object.fromEntries(
  CHAIN_MACHINE_GROUPS.map((group) => [group.id, [...group.machineIds]]),
) as Record<MachinePreferenceGroup, string[]>;

export function machinePreferenceGroupForRecipe(
  recipe: Recipe,
): MachinePreferenceGroup {
  const categories = new Set(recipe.categories);
  if (
    categories.has("chemistry") ||
    categories.has("cryogenics") ||
    categories.has("oil-processing")
  )
    return "fluids";
  if (categories.has("smelting") || categories.has("metallurgy"))
    return "smelting";
  if (categories.has("organic") || categories.has("captive-spawner-process"))
    return "biology";
  if (
    categories.has("crafting") ||
    categories.has("advanced-crafting") ||
    categories.has("crafting-with-fluid") ||
    categories.has("electromagnetics")
  )
    return "crafting";
  return "processing";
}

export function unlockedMachineSet(
  master?: Pick<ChainMasterSettings, "unlockedMachineIds">,
): Set<string> {
  return new Set(master?.unlockedMachineIds ?? DEFAULT_UNLOCKED_MACHINE_IDS);
}

export function rankedMachinesForRecipe(
  recipe: Recipe,
  master?: Pick<
    ChainMasterSettings,
    "unlockedMachineIds" | "machinePriorities" | "preferredMachineId"
  >,
): Machine[] {
  const compatible = machinesForRecipe(recipe);
  if (!master) return compatible;
  const unlocked = unlockedMachineSet(master);
  const available = compatible.filter(
    (machine) => machine.energyKind === "passive" || unlocked.has(machine.id),
  );
  if (!available.length) return [];
  if (master.preferredMachineId !== "auto") {
    const preferred = available.find(
      (machine) => machine.id === master.preferredMachineId,
    );
    if (preferred)
      return [preferred, ...available.filter((machine) => machine !== preferred)];
  }
  const group = machinePreferenceGroupForRecipe(recipe);
  const priority = master.machinePriorities?.[group] ??
    DEFAULT_MACHINE_PRIORITIES[group];
  const rank = new Map(priority.map((id, index) => [id, index]));
  return [...available].sort((a, b) => {
    const aRank = rank.get(a.id) ?? Number.MAX_SAFE_INTEGER;
    const bRank = rank.get(b.id) ?? Number.MAX_SAFE_INTEGER;
    return aRank - bRank || compatible.indexOf(a) - compatible.indexOf(b);
  });
}

/**
 * Keeps the canonical recipe as the automatic baseline, then falls back to an
 * alternative product route only when no unlocked machine can make it.
 * Explicit per-product route choices remain available in the chain panel.
 */
export function preferredUnlockedChainRoute(
  product: ProductOption,
  master?: ChainMasterSettings,
): { recipe: Recipe; machine: Machine } | undefined {
  const canonical = preferredChainRecipeForProduct(product);
  const canonicalMachine = rankedMachinesForRecipe(canonical, master)[0];
  if (canonicalMachine) return { recipe: canonical, machine: canonicalMachine };
  for (const recipeId of product.recipeIds) {
    const recipe = RECIPE_BY_ID.get(recipeId);
    if (!recipe) continue;
    const machine = rankedMachinesForRecipe(recipe, master)[0];
    if (machine) return { recipe, machine };
  }
  return undefined;
}
