import assert from "node:assert/strict";
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import test, { after } from "node:test";
import { fileURLToPath } from "node:url";

import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { createServer } from "vite";

const root = fileURLToPath(new URL("..", import.meta.url));
const vite = await createServer({
  appType: "custom",
  configFile: false,
  root,
  resolve: { alias: { "@": root } },
  server: { middlewareMode: true },
});

after(async () => {
  await vite.close();
});

async function readCssTree(directory) {
  const entries = await readdir(directory, { withFileTypes: true });
  const contents = await Promise.all(
    entries.map(async (entry) => {
      const entryPath = path.join(directory, entry.name);
      if (entry.isDirectory()) {
        return readCssTree(entryPath);
      }
      return entry.name.endsWith(".css") ? readFile(entryPath, "utf8") : "";
    }),
  );
  return contents.join("\n");
}

test("emits the calculator's layout and hierarchy styles", async () => {
  const css = await readCssTree(path.join(root, "dist"));

  assert.match(css, /--tw-enter-opacity/);
  assert.match(css, /scrollbar-width:\s*none/);
  assert.match(css, /scrollbar-gutter:\s*stable/);
  assert.match(css, /min-height:\s*6\.25rem/);
  assert.match(css, /flex-shrink:\s*0/);
  assert.match(css, /product-scope/);
  assert.match(css, /chain-node/);
  assert.match(css, /summary-leading/);
  assert.match(css, /summary-trailing/);
  assert.match(css, /factory-totals-section-summary/);
  assert.match(css, /prefers-reduced-motion:\s*reduce/);
});

test("renders quality output as explicit icon tiles beside the product", async () => {
  const { MaterialRow } = await vite.ssrLoadModule(
    "/app/components/calculator-widgets.tsx",
  );
  const html = renderToStaticMarkup(
    React.createElement(MaterialRow, {
      flow: {
        id: "advanced-circuit",
        name: "Advanced circuit",
        type: "item",
        icon: "/factorio-icons/base/icons/advanced-circuit.png",
        total: 10,
        amounts: [
          { quality: "normal", amount: 8 },
          { quality: "uncommon", amount: 2 },
        ],
      },
      unit: "s",
      locale: "en",
    }),
  );
  assert.match(html, /quality-output-grid/);
  assert.equal((html.match(/quality-output-chip/g) ?? []).length, 2);
  assert.match(html, /Normal/);
  assert.match(html, /Uncommon/);
  assert.match(html, /Σ/);
});

test("renders custom number steppers and zoom controls", async () => {
  const { GeometryDesign, SmartNumberInput } = await vite.ssrLoadModule(
    "/app/components/calculator-widgets.tsx",
  );
  const number = renderToStaticMarkup(
    React.createElement(SmartNumberInput, {
      value: 12,
      onValueChange() {},
    }),
  );
  assert.match(number, /smart-number-buttons/);
  assert.match(number, /aria-label="Increase"/);
  assert.match(number, /aria-label="Decrease"/);
  const geometry = renderToStaticMarkup(
    React.createElement(GeometryDesign, {
      locale: "en",
      layout: {
        requestedPerMachine: 1,
        machineCount: 1,
        counts: [1],
        average: 1,
        min: 1,
        max: 1,
        uniqueBeacons: 1,
        exact: true,
        extrapolated: false,
        corridor: 2,
        placements: [],
        bounds: { minX: 0, maxX: 1, minY: 0, maxY: 1 },
      },
    }),
  );
  assert.match(geometry, /aria-label="Zoom"/);
  assert.match(geometry, /100%/);
});

test("all product searches expose inventory tabs and official subgroups", async () => {
  const picker = await readFile(
    path.join(root, "app/components/search-picker.tsx"),
    "utf8",
  );
  const production = await readFile(
    path.join(root, "app/components/calculator-app.tsx"),
    "utf8",
  );
  const recycling = await readFile(
    path.join(root, "app/components/recycling-workspace.tsx"),
    "utf8",
  );
  const rocket = await readFile(
    path.join(root, "app/components/rocket-freight-workspace.tsx"),
    "utf8",
  );
  assert.match(picker, /role="tablist"/);
  assert.match(picker, /optionSections/);
  assert.doesNotMatch(picker, /lg:inline/);
  assert.match(picker, /title=\{group\.label\}/);
  assert.match(production, /subgroupLabel/);
  assert.match(production, /OutputMaterialPicker/);
  assert.match(production, /triggerContent/);
  assert.match(recycling, /subgroupLabel/);
  assert.match(recycling, /RecyclingOutputPicker/);
  assert.match(rocket, /subgroupLabel/);
});

test("science results keep an independent per-minute output unit by default", async () => {
  const source = await readFile(
    path.join(root, "app/components/advanced-panels.tsx"),
    "utf8",
  );
  assert.match(
    source,
    /const \[outputUnit, setOutputUnit\] = useState<RateUnit>\("min"\)/,
  );
  assert.match(
    source,
    /<UnitSelect value=\{outputUnit\} onChange=\{setOutputUnit\}/,
  );
  assert.match(source, /outputUnit === "s"/);
  assert.match(source, /biolabBeaconMode === "manual"/);
  assert.doesNotMatch(source, /tr\(locale, "biolabRingNote"\)/);
});

test("all chain-wide module priorities fit without moving the shared right edge", async () => {
  const css = await readFile(path.join(root, "app/globals.css"), "utf8");
  assert.match(
    css,
    /\.chain-master-panel\s*\{[^}]*container-type:\s*inline-size[^}]*overflow:\s*clip/s,
  );
  assert.match(
    css,
    /\.chain-master-panel \.dashboard-summary \.summary-trailing\s*\{[^}]*width:\s*max-content/s,
  );
  assert.match(
    css,
    /\.chain-master-panel \.priority-kpi\s*\{[^}]*width:\s*max-content[^}]*min-width:\s*max-content[^}]*max-width:\s*none[^}]*flex:\s*0 0 auto/s,
  );
  assert.match(
    css,
    /\.chain-master-panel \.priority-kpi strong\s*\{[^}]*white-space:\s*nowrap/s,
  );
  assert.doesNotMatch(css, /\.chain-master-panel \.priority-kpi strong\s*\{[^}]*font-size:/s);
  assert.match(css, /@container \(max-width: 48rem\)/);
  assert.match(css, /@container \(max-width: 28rem\)/);
});

test("production chain starts with ingredients and exposes chain-wide controls", async () => {
  const source = await readFile(
    path.join(root, "app/components/advanced-panels.tsx"),
    "utf8",
  );
  assert.match(source, /chain\.root\.inputs\.map/);
  assert.match(source, /ChainMasterPanel/);
  assert.match(source, /preferredMachineId/);
  assert.match(source, /processMachineChoices/);
  assert.match(source, /subprocessMachines/);
  assert.match(source, /machineModuleFamilies/);
  assert.match(source, /moduleItemId\("speed", selectedBeaconModuleTier\)/);
  assert.match(source, /moduleItemId\("efficiency", master\.beaconModuleTier\)/);
  assert.match(source, /iconFor\("solar-system-edge"\)/);
  assert.match(source, /qualityBeaconMode/);
  assert.match(source, /qualityMode=\{qualityMode\}/);
  assert.match(source, /InserterSettingsFields/);
  assert.match(source, /inserterCapacityBonus/);
  assert.match(source, /node\.logisticsSettings/);
});

test("multi-product workspaces promote selected products and render global chain and factory totals", async () => {
  const production = await readFile(
    path.join(root, "app/components/calculator-app.tsx"),
    "utf8",
  );
  const panels = await readFile(
    path.join(root, "app/components/advanced-panels.tsx"),
    "utf8",
  );
  assert.match(production, /const promote = \(id: string\)/);
  assert.match(production, /items\[index\], \.\.\.items\.slice\(0, index\)/);
  assert.match(production, /<ChainMasterPanel[\s\S]*global/);
  assert.match(production, /clearOverridesForMasterPatch/);
  assert.match(production, /<FactoryTotalsPanel/);
  assert.match(production, /target\.chainEnabled && baseResult\.valid/);
  assert.match(production, /chainEnabled: template\?\.chainEnabled \?\? true/);
  assert.match(production, /includeProductionChain/);
  assert.doesNotMatch(production, /const chainModeActive = targets\.some/);
  assert.match(production, /resultCollapsed/);
  assert.match(production, /allChainsGenerated/);
  assert.match(panels, /buildFactoryTotals\(snapshots\)/);
  assert.match(panels, /combinedResources/);
  assert.match(panels, /productionProcesses/);
  assert.match(panels, /inserter-total-card/);
  assert.match(panels, /BeltThroughputPanel/);
  assert.match(panels, /splitSharedStreams/);
  assert.match(panels, /calculateBeltLoad/);
  assert.match(panels, /contribution\.inserterLoads/);
  assert.match(panels, /machineResearchAndPriority/);
  assert.match(panels, /unlockedMachineIds/);
  assert.match(panels, /machinePriorities/);
  assert.match(panels, /constructionSummary/);
  assert.match(panels, /load\.inputFlows/);
  assert.match(panels, /totalBeltLoad/);
  assert.match(panels, /MINING_DRILLS\.map/);
  assert.match(panels, /miningDrillId/);
  assert.match(panels, /miningModuleFamily/);
  assert.match(panels, /miningQualityChance/);
  assert.match(panels, /node\.outputAmounts/);
  assert.match(panels, /mostUsedRateUnit\(targetUnits\)/);
  assert.match(panels, /<CollapsiblePanel className="factory-totals/);
  assert.match(panels, /selected-throughput-kpi/);
  assert.match(panels, /SummaryKpi label="Σ" value=\{totals\.resources\.length\}/);
  assert.match(panels, /data-totals-section="resources"/);
  assert.match(panels, /data-totals-section="processes"/);
  assert.match(panels, /data-totals-section="belts"/);
  assert.match(panels, /data-totals-section="construction"/);
  assert.match(panels, /data-totals-entry="construction-group"/);
  assert.match(production, /applyMasterToMainTarget/);
  assert.match(production, /copyModuleConfig\(template\.machineConfig\)/);
  assert.match(production, /qualityMode[\s\S]*preferredChainRecipeForProduct/);
});

test("release information is shared by every visible calculator tab", async () => {
  const app = await readFile(
    path.join(root, "app/components/calculator-app.tsx"),
    "utf8",
  );
  const footer = await readFile(
    path.join(root, "app/components/release-footer.tsx"),
    "utf8",
  );
  const help = await readFile(
    path.join(root, "app/components/help-workspace.tsx"),
    "utf8",
  );
  assert.match(app, /<ReleaseFooter/);
  assert.ok(app.indexOf("<ReleaseFooter") > app.indexOf("<HelpWorkspace"));
  assert.match(footer, /href="\/license\.html"/);
  assert.match(footer, /href="\/third-party-notices\.html"/);
  assert.match(footer, /factoriocalculatorapp\.com@gmail\.com/);
  assert.doesNotMatch(help, /<footer/);
});

test("every disclosure uses the shared full-panel click header", async () => {
  const sources = await Promise.all(
    [
      "app/components/advanced-panels.tsx",
      "app/components/calculator-app.tsx",
      "app/components/calculator-widgets.tsx",
      "app/components/help-workspace.tsx",
    ].map((file) => readFile(path.join(root, file), "utf8")),
  );
  const source = sources.join("\n");
  const summaries = source.match(/<summary\b/g) ?? [];
  const sharedHeaders =
    source.match(/<summary className="collapsible-summary\b/g) ?? [];

  assert.ok(summaries.length > 10);
  assert.equal(sharedHeaders.length, summaries.length);
  assert.match(source, /function CollapsiblePanel/);
  assert.match(source, /onToggle=\{\(event\) => setIsOpen\(event\.currentTarget\.open\)\}/);
  assert.match(source, /dashboard-summary/);
  assert.match(source, /factory-totals-section-summary/);
});

test("integrated Help covers every calculator and all supported languages", async () => {
  const { HELP_COPY } = await vite.ssrLoadModule("/app/lib/help-content.ts");
  const locales = [
    "en",
    "de",
    "es-ES",
    "fr",
    "it",
    "ja",
    "ru",
    "zh-CN",
    "zh-TW",
  ];
  const requiredTopics = [
    "science",
    "production",
    "quality",
    "recycling",
    "quality-recycling",
    "rocket",
    "energy",
    "chains",
    "chain-wide",
    "modules",
    "logistics",
    "totals",
    "blueprints",
    "warnings",
    "faq",
  ];

  assert.deepEqual(Object.keys(HELP_COPY), locales);
  for (const locale of locales) {
    assert.equal(HELP_COPY[locale].steps.length, 6);
    assert.equal(HELP_COPY[locale].topics.length, 17);
    assert.equal(new Set(HELP_COPY[locale].topics.map((topic) => topic.id)).size, 17);
    for (const topic of HELP_COPY[locale].topics) {
      if (topic.id === "blueprints") assert.ok(topic.points.length >= 10);
      else assert.equal(topic.details.length, 3);
    }
    const topicIds = new Set(HELP_COPY[locale].topics.map((topic) => topic.id));
    for (const topic of requiredTopics) assert.ok(topicIds.has(topic));
    assert.match(HELP_COPY[locale].versionLabel, /1\.0\.0/);
    assert.ok(HELP_COPY[locale].unofficialNotice.length > 80);
  }

  const app = await readFile(
    path.join(root, "app/components/calculator-app.tsx"),
    "utf8",
  );
  assert.match(app, /id: "help"/);
  assert.match(app, /<HelpWorkspace locale=\{locale\}/);
  assert.ok(app.indexOf('id: "help"') > app.indexOf('id: "energy"'));
});

test("Rocket freight exposes quality storage and all three loading assumptions", async () => {
  const source = await readFile(
    path.join(root, "app/components/rocket-freight-workspace.tsx"),
    "utf8",
  );
  assert.match(source, /cargoBayQuality/);
  assert.match(source, /loadingMode.*"robots" \| "inserters" \| "ignore"/s);
  assert.match(source, /max=\{300\}/);
  assert.match(source, /bufferChestAssumption/);
  assert.match(source, /payloadWaveTitle/);
});

test("production machine heading has no compatibility counter and quality fluid note is unobtrusive", async () => {
  const production = await readFile(
    path.join(root, "app/components/calculator-app.tsx"),
    "utf8",
  );
  const recycling = await readFile(
    path.join(root, "app/components/recycling-workspace.tsx"),
    "utf8",
  );
  assert.doesNotMatch(production, /productionMachine[^\n]*compatible\.length/);
  assert.match(recycling, /quality-fluid-footnote/);
});
