import { MODULE_BY_KEY, QUALITY_BY_ID } from "./factorio-data.ts";
import type {
  EffectKind,
  MiningDrillId,
  ModuleFamily,
  ModuleSelection,
  QualityId,
} from "./types.ts";

export interface MiningDrillDefinition {
  id: MiningDrillId;
  miningSpeed: number;
  electricKw: number;
  moduleSlots: number;
  supportedResourceKeys: readonly string[];
}

export interface MiningModuleSetup {
  family: ModuleFamily | "none";
  tier: 1 | 2 | 3;
  quality: QualityId;
}

export interface MiningSourceSizing {
  drill: MiningDrillDefinition;
  rate: number;
  electricKw: number;
  modules: Array<ModuleSelection | null>;
  qualityChance: number;
}

const BASIC_RESOURCES = [
  "item:iron-ore",
  "item:copper-ore",
  "item:coal",
  "item:stone",
] as const;

const ELECTRIC_RESOURCES = [
  ...BASIC_RESOURCES,
  "item:uranium-ore",
  "item:calcite",
  "item:scrap",
] as const;

const ALL_MINED_RESOURCES = [
  ...ELECTRIC_RESOURCES,
  "item:tungsten-ore",
] as const;

export const DEFAULT_MINING_DRILL_ID: MiningDrillId = "big-mining-drill";

/**
 * Base prototype values for Factorio 2.1.17. Burner power is deliberately not
 * counted as electricity; the production-chain power total is an electrical
 * load ledger.
 */
export const MINING_DRILLS: readonly MiningDrillDefinition[] = [
  {
    id: "burner-mining-drill",
    miningSpeed: 0.25,
    electricKw: 0,
    moduleSlots: 0,
    supportedResourceKeys: BASIC_RESOURCES,
  },
  {
    id: "electric-mining-drill",
    miningSpeed: 0.5,
    electricKw: 90,
    moduleSlots: 3,
    supportedResourceKeys: ELECTRIC_RESOURCES,
  },
  {
    id: "big-mining-drill",
    miningSpeed: 2.5,
    electricKw: 300,
    moduleSlots: 4,
    supportedResourceKeys: ALL_MINED_RESOURCES,
  },
] as const;

const MINING_DRILL_BY_ID = new Map(
  MINING_DRILLS.map((drill) => [drill.id, drill]),
);

export function resolveMiningDrill(
  resourceKey: string,
  requestedId: MiningDrillId = DEFAULT_MINING_DRILL_ID,
): MiningDrillDefinition {
  const requestedIndex = Math.max(
    0,
    MINING_DRILLS.findIndex((drill) => drill.id === requestedId),
  );
  return (
    MINING_DRILLS.slice(requestedIndex).find((drill) =>
      drill.supportedResourceKeys.includes(
        resourceKey as (typeof drill.supportedResourceKeys)[number],
      ),
    ) ?? MINING_DRILL_BY_ID.get(DEFAULT_MINING_DRILL_ID)!
  );
}

export function miningSourceForBaseline(
  resourceKey: string,
  bigDrillRate: number,
  requestedId: MiningDrillId = DEFAULT_MINING_DRILL_ID,
  moduleSetup: MiningModuleSetup = {
    family: "none",
    tier: 3,
    quality: "normal",
  },
): MiningSourceSizing {
  const drill = resolveMiningDrill(resourceKey, requestedId);
  const selection: ModuleSelection | null =
    moduleSetup.family === "none"
      ? null
      : {
          family: moduleSetup.family,
          tier: moduleSetup.tier,
          quality: moduleSetup.quality,
        };
  const modules = Array.from({ length: drill.moduleSlots }, () =>
    selection ? { ...selection } : null,
  );
  const effects: Partial<Record<EffectKind, number>> = {};
  for (const installedModule of modules) {
    if (!installedModule) continue;
    const definition = MODULE_BY_KEY.get(
      `${installedModule.family}:${installedModule.tier}`,
    );
    const quality = QUALITY_BY_ID.get(installedModule.quality);
    if (!definition || !quality) continue;
    for (const [effect, rawValue] of Object.entries(definition.effects) as Array<
      [EffectKind, number]
    >) {
      const scalesWithQuality =
        (effect === "consumption" && rawValue < 0) ||
        (effect === "speed" && rawValue > 0) ||
        (effect === "productivity" && rawValue > 0) ||
        (effect === "pollution" && rawValue < 0) ||
        (effect === "quality" && rawValue > 0);
      effects[effect] =
        (effects[effect] ?? 0) +
        rawValue * (scalesWithQuality ? quality.multiplier : 1);
    }
  }
  const speedFactor = Math.max(0.2, 1 + (effects.speed ?? 0));
  const productivityFactor = Math.max(0, 1 + (effects.productivity ?? 0));
  const consumptionFactor = Math.max(0.2, 1 + (effects.consumption ?? 0));
  return {
    drill,
    rate:
      bigDrillRate *
      (drill.miningSpeed / 2.5) *
      speedFactor *
      productivityFactor,
    electricKw: drill.electricKw * consumptionFactor,
    modules,
    qualityChance: Math.max(0, Math.min(1, effects.quality ?? 0)),
  };
}
