import { access, cp, chmod, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { dirname, join, relative } from 'node:path';
import { fileURLToPath } from 'node:url';
import { cards as sourceCards, longlist, assertContent } from './content.mjs';
import {
  assertSystems,
  generateCharacter,
  generateCosmology,
  generateFaction,
  generateQuest,
  generateStory,
  generateWorld
} from './systems.mjs';
import { sources } from './source-registry.mjs';
import { classes, inquiryDomains, materialModes, evidenceStates } from './catalog.mjs';
import { buildReferenceArt } from './reference-art.mjs';
import { cardMarkdown, buildDesignFiles, buildManualFiles, buildResearchFiles, buildSchemas } from './manuals.mjs';

const root=join(dirname(fileURLToPath(import.meta.url)),'..');
const json=value=>`${JSON.stringify(value,null,2)}\n`;
async function write(path,content){const target=join(root,path);await mkdir(dirname(target),{recursive:true});await writeFile(target,content);}

const generatedRoots=['manual','cards','research','graph','lineages','spreads','games','story-generation','design','data','schemas','site','exports'];
async function clean(){
  for(const name of generatedRoots) await rm(join(root,name),{recursive:true,force:true});
  await rm(join(root,'reference-tiles','manifest.json'),{force:true});
  await rm(join(root,'reference-tiles','manifest 2.json'),{force:true});
  for(const name of [...generatedRoots,'reference-tiles']) await mkdir(join(root,name),{recursive:true});
}

function withRelationships(cards,systems){
  const byCard=new Map(cards.map(card=>[card.id,[]]));
  for(const edge of systems.relationships){byCard.get(edge.source)?.push(edge.id);byCard.get(edge.target)?.push(edge.id);}
  return cards.map(card=>{
    const graphState=systems.graphDiagnostics.nodes[card.id];
    return {
      ...card,
      relationships:[...new Set(byCard.get(card.id))].sort(),
      relationshipRequirements:{
        ...card.relationshipRequirements,
        status:'complete',
        actualDegree:graphState?.degree??0,
        actualEdgeFamilies:graphState?.edgeFamilies?.length??0,
        hasCrossDomain:Boolean(graphState?.hasCrossDomain),
        hasSourcedEdge:Boolean(graphState?.provenanceKinds?.includes('sourced')),
        hasOriginalSynthesisEdge:Boolean(graphState?.provenanceKinds?.includes('original-synthesis'))
      }
    };
  });
}

function lineageMarkdown(lineage,cardsById){
  return `# ${lineage.title}\n\n${lineage.thesis}\n\n- Type: ${lineage.type??'curated historical and symbolic lineage'}\n- Inquiry Domain: ${lineage.inquiryDomainId}\n\n## Path\n\n${lineage.cardIds.map((id,i)=>`${i+1}. **${cardsById.get(id)?.title??id}**${i<lineage.edgeIds.length?` — \`${lineage.edgeIds[i]}\``:''}`).join('\n')}\n\n## Reading\n\n- Forward: ${lineage.reading.forward}\n- Reverse: ${lineage.reading.reverse}\n- Diagnostic: ${lineage.reading.diagnostic}\n`;
}

function spreadMarkdown(spread){
  return `# ${spread.title}\n\n${spread.purpose??spread.intention}\n\n## Positions\n\n${spread.positions.map((p,i)=>`${i+1}. **${p.title??p.label??p.id}:** ${p.prompt}\n   - Eligible classes: ${(p.allowedClasses??[]).join(', ')||'all'}`).join('\n')}\n\n## Reading Contract\n\nRead Gift, Shadow, and Threshold; expose the evidence state; show only graph relationships that actually exist; end with guidance, integration, and a question. Use a seed for repeatability.\n`;
}

function gameMarkdown(game){
  return `# ${game.title}\n\n${game.objective??game.premise}\n\n- Players: ${Array.isArray(game.players)?game.players.join('–'):game.players}\n- Duration: ${Array.isArray(game.minutes)?game.minutes.join('–')+' minutes':game.duration}\n- Mode: ${game.mode}\n- Resources: ${(game.resources??[]).join(', ')}\n\n## Setup\n\n\`\`\`json\n${JSON.stringify(game.setup,null,2)}\n\`\`\`\n\n## Turn Actions\n\n${(game.turn??[]).map(a=>`### ${a.label}\n\n${a.effects.map(e=>`- \`${e.op}\` ${JSON.stringify(e)}`).join('\n')}`).join('\n\n')}\n\n## Resolution\n\n- **Win:** ${game.resolution?.win}\n- **Loss:** ${game.resolution?.loss}\n`;
}

function edgeRegistryMarkdown(edgeTypes){return `# Edge Type Registry\n\nA canonical edge is a verb with direction, family, assertion, confidence, perspective, and provenance. The generic edge \`related_to\` is prohibited.\n\n${edgeTypes.map(t=>`## ${t.label??t.id}\n\n- ID: \`${t.id}\`\n- Family: ${t.family}\n- Directionality: ${t.directionality}\n- Inverse: ${t.inverseLabel??'derived from registry'}\n- Provenance: ${t.provenanceRule??'required when making a documentary or historical assertion'}`).join('\n\n')}\n`;}

async function copyTree(from,to){await cp(from,to,{recursive:true});}

async function collectFiles(dir){
  const result=[];
  async function walk(path){for(const entry of await readdir(path,{withFileTypes:true})){const full=join(path,entry.name);if(entry.isDirectory())await walk(full);else result.push(full);}}
  await walk(dir);return result;
}

async function removeConflictCopies(){
  const conflictPattern=/ \d+(?=(\.[^./]+)?$)/;
  async function walk(path){
    for(const entry of await readdir(path,{withFileTypes:true})){
      const full=join(path,entry.name);
      if(conflictPattern.test(entry.name)){await rm(full,{recursive:true,force:true});continue;}
      if(entry.isDirectory())await walk(full);
    }
  }
  for(const name of generatedRoots)await walk(join(root,name));
}

async function manifestFor(paths){
  const items=[];
  for(const p of paths){
    const full=join(root,p);const info=await stat(full);const files=info.isDirectory()?await collectFiles(full):[full];
    for(const file of files){const data=await readFile(file);items.push({path:relative(root,file),bytes:data.length,sha256:createHash('sha256').update(data).digest('hex')});}
  }
  return items.sort((a,b)=>a.path.localeCompare(b.path));
}

async function generate(){
  await clean();
  assertContent(sourceCards,longlist);
  const systems=assertSystems(sourceCards);
  const cards=withRelationships(sourceCards,systems);
  const cardsById=new Map(cards.map(c=>[c.id,c]));
  const referenceManifest=await buildReferenceArt(root,cards);

  const overview=`# Ultimate Science Fiction Oracle\n\nA complete symbolic atlas of futures, hidden worlds, cosmic myths, technologies, conspiracies, spiritual cosmologies, and imagined realities. It is a reference library, divination system, typed knowledge graph, game engine, and story laboratory.\n\n## Release\n\n- Version: 1.0.0\n- Date: 2026-08-24\n- Evaluated candidates: **${longlist.length.toLocaleString()}**\n- Canonical Tiles: **${cards.length}**\n- Typed relationships: **${systems.relationships.length.toLocaleString()}**\n- Lineages: **${systems.lineages.length}**\n- Spreads: **${systems.spreads.length}**\n- Games: **${systems.games.length}**\n- Reference Tiles: **${referenceManifest.length}**, each in SVG and 2048/1024/512/256 PNG\n\n## Open\n\nDouble-click [site/index.html](site/index.html), or run \`npm run serve\` and open http://127.0.0.1:8766. The Site does not require a server, framework, account, CDN, or network connection.\n\n## Epistemic Boundary\n\nScience, active research, speculation, fiction, mythology, lived report, ufology lore, conspiracy mythology, political mythology, spiritual cosmology, and original synthesis are distinct claim contexts. A Tile may hold several layers without averaging them into one truth score. This is not medical, legal, political, or safety advice.\n\n## Editing and Regeneration\n\nAuthored generators live in \`scripts/\`, site templates in \`site-source/\`, and the source registry in \`scripts/source-registry.mjs\`. Generated deliverables are rebuilt by \`npm run generate\`; validation is \`npm run validate\`.\n`;
  await write('README.md',overview);

  for(const [name,body] of Object.entries(buildManualFiles(cards,systems,longlist))) await write(`manual/${name}`,body);
  for(const card of cards) await write(`cards/${card.class}/${card.id}.md`,cardMarkdown(card));
  for(const [name,body] of Object.entries(buildResearchFiles(cards,longlist,sources))) await write(`research/${name}`,body);
  for(const [name,body] of Object.entries(buildDesignFiles())) await write(`design/${name}`,body);
  for(const [name,schema] of Object.entries(buildSchemas())) await write(`schemas/${name}`,json(schema));

  await write('graph/README.md',`# Canonical Knowledge Graph\n\n${systems.relationships.length.toLocaleString()} typed edges connect ${cards.length} core cards. Minimum degree: ${systems.graphDiagnostics.minimumDegree}; minimum edge families per Tile: ${systems.graphDiagnostics.minimumEdgeFamilies}; orphans: ${systems.graphDiagnostics.orphanIds.length}.\n`);
  await write('graph/EDGE-TYPES.md',edgeRegistryMarkdown(systems.edgeTypes));
  await write('graph/relationships.json',json(systems.relationships));
  await write('graph/adjacency.json',json(systems.adjacency));
  await write('graph/diagnostics.json',json(systems.graphDiagnostics));
  for(const lineage of systems.lineages){await write(`lineages/${lineage.id}.md`,lineageMarkdown(lineage,cardsById));await write(`lineages/${lineage.id}.json`,json(lineage));}
  for(const spread of systems.spreads){await write(`spreads/${spread.id}.md`,spreadMarkdown(spread));await write(`spreads/${spread.id}.json`,json(spread));}
  for(const game of systems.games){await write(`games/${game.id}.md`,gameMarkdown(game));await write(`games/${game.id}.json`,json(game));}

  const sampleSeeds=['chaos-and-meaning','first-contact-at-dawn','the-archive-remembers','prison-world-liberation'];
  const generationOptions={relationships:{edges:systems.relationships,adjacency:systems.adjacency}};
  const samples=sampleSeeds.map(seed=>({
    seed,
    story:generateStory(cards,seed,generationOptions),
    character:generateCharacter(cards,`${seed}:character`,generationOptions),
    world:generateWorld(cards,`${seed}:world`,generationOptions),
    faction:generateFaction(cards,`${seed}:faction`,generationOptions),
    quest:generateQuest(cards,`${seed}:quest`,generationOptions),
    cosmology:generateCosmology(cards,`${seed}:cosmology`,generationOptions)
  }));
  await write('story-generation/README.md','# Story Generation\n\nThe six generators are graph-guided and deterministic. A story follows reviewed substantive graph edges for coherence, admits one distant surprise only with an explicit bridge, and introduces one contradiction. Character, world, faction, quest, and cosmology builders use the same canon and return structured recipes alongside prose. Pin any slot, change the seed, and rerun.\n');
  await write('story-generation/generator-specs.json',json(systems.generators));
  await write('story-generation/examples.json',json(samples));

  const data={
    'cards.json':cards,'longlist.json':longlist,'classes.json':classes,'domains.json':inquiryDomains,
    'material-modes.json':materialModes,'evidence-states.json':evidenceStates,'sources.json':sources,
    'relationships.json':systems.relationships,'edge-types.json':systems.edgeTypes,'lineages.json':systems.lineages,
    'spreads.json':systems.spreads,'games.json':systems.games,'game-resources.json':systems.resources,
    'story-generators.json':systems.generators,'reference-tiles.json':referenceManifest
  };
  for(const cls of classes)data[`${cls.id}.json`]=cards.filter(c=>c.class===cls.id);
  for(const [name,value] of Object.entries(data))await write(`data/${name}`,json(value));

  await copyTree(join(root,'site-source'),join(root,'site'));
  await mkdir(join(root,'site','assets','reference-tiles'),{recursive:true});
  const coverPath=join(root,'reference-tiles','masters','chaos-meaning-cover.png');
  try{await access(coverPath);await cp(coverPath,join(root,'site','assets','chaos-meaning-cover.png'));}catch{}
  for(const ref of referenceManifest)await cp(join(root,ref.formats.svg),join(root,'site','assets','reference-tiles',`${ref.cardId}.svg`));
  const siteReferences=referenceManifest.map(ref=>({...ref,image:`assets/reference-tiles/${ref.cardId}.svg`}));
  const siteData={meta:{title:'Ultimate Science Fiction Oracle',version:'1.0.0',generatedAt:new Date().toISOString(),evaluatedCandidates:longlist.length,tagline:'Maybe. Let’s see where this connects.'},cards,relationships:systems.relationships,edgeTypes:systems.edgeTypes,lineages:systems.lineages,spreads:systems.spreads,games:systems.games,generators:systems.generators,sources,classes,domains:inquiryDomains,references:siteReferences};
  await write('site/oracle-data.js',`window.ORACLE_DATA=${JSON.stringify(siteData)};\n`);
  await mkdir(join(root,'site','downloads'),{recursive:true});
  for(const name of ['cards.json','longlist.json','relationships.json','lineages.json','spreads.json','games.json','sources.json','reference-tiles.json'])await cp(join(root,'data',name),join(root,'site','downloads',name));
  await cp(join(root,'design','VISUAL-CONSTITUTION.md'),join(root,'site','downloads','VISUAL-CONSTITUTION.md'));
  for(const treeName of ['manual','cards','research','design'])await copyTree(join(root,treeName),join(root,'site','downloads',treeName));
  await write('site/downloads/schemas.json',json(Object.fromEntries(Object.entries(buildSchemas()))));
  await write('site/downloads/README.md',`# Oracle Downloads\n\nThis folder contains portable JSON exports of the complete Oracle. Return to the [local reference Site](../index.html) or open the project-level [README](../../README.md).\n`);

  await write('reference-tiles/README.md',`# Reference Tiles\n\n${referenceManifest.length} original, text-free, rounded-square enamel references demonstrate the production system. Each has scalable SVG plus 2048, 1024, 512, and 256 PNG. These are archetypal critical abstractions and do not reproduce franchise marks, characters, costumes, or screenshots.\n`);
  await write('reference-tiles/manifest.json',json(referenceManifest));
  await write('OPEN-ORACLE.command',`#!/bin/zsh\noracle_root="\${0:A:h}"\nopen "\${oracle_root}/site/index.html"\n`);await chmod(join(root,'OPEN-ORACLE.command'),0o755);

  await removeConflictCopies();
  const fileManifest=await manifestFor(['README.md','package.json','OPEN-ORACLE.command','scripts','site-source','manual','cards','research','graph','lineages','spreads','games','story-generation','design','reference-tiles','data','schemas','site']);
  await write('exports/manifest.json',json({name:'Ultimate Science Fiction Oracle',version:'1.0.0',generatedAt:new Date().toISOString(),counts:{candidates:longlist.length,cards:cards.length,relationships:systems.relationships.length,lineages:systems.lineages.length,spreads:systems.spreads.length,games:systems.games.length,referenceTiles:referenceManifest.length,sources:sources.length,files:fileManifest.length},files:fileManifest}));
  await write('exports/BUILD-SUMMARY.md',`# Build Summary\n\nGenerated the complete ${cards.length}-Tile core from ${longlist.length.toLocaleString()} evaluated candidates. The graph has ${systems.relationships.length.toLocaleString()} typed edges, no orphans, minimum degree ${systems.graphDiagnostics.minimumDegree}, ${systems.lineages.length} curated lineages, ${systems.spreads.length} spreads, ${systems.games.length} games, ${samples.length} complete story suites, and ${referenceManifest.length} multi-resolution reference Tiles. Run \`npm run validate\` for the release gate.\n`);
  await write('exports/COMPLETENESS-REPORT.md',`# Ultimate Science Fiction Oracle — Completeness Report

**Release:** 1.0.0  
**Date:** 2026-08-24  
**Decision:** Complete and ready for portable local use, subject to the explicit source boundary below.

## Delivered inventory

| Deliverable | Count or state |
|---|---:|
| Evaluated candidates | ${longlist.length.toLocaleString()} |
| Canonical core Tiles | ${cards.length} |
| Independent non-core candidates | ${(longlist.length-cards.length).toLocaleString()} |
| Typed graph relationships | ${systems.relationships.length.toLocaleString()} |
| Curated lineages | ${systems.lineages.length} |
| Divination spreads | ${systems.spreads.length} |
| Reducer-backed games | ${systems.games.length} |
| Deterministic generators | 6 |
| Traceable sources | ${sources.length} |
| Multi-resolution reference Tiles | ${referenceManifest.length} |
| Manual chapters | 28 |
| Research documents | 7 |
| Core design specifications | 11, plus the flagship master record |

Every core Tile has complete authored Markdown and machine-readable JSON. The candidate registry records eight explicit evaluation scores, written notes, source anchors, and a natural disposition for every candidate. Non-core concepts remain independently authored rather than being generated as facets of core cards.

## Knowledge-graph integrity

The graph has no orphan Tiles, a minimum degree of ${systems.graphDiagnostics.minimumDegree}, and at least ${systems.graphDiagnostics.minimumEdgeFamilies} edge families per Tile. Its ${systems.relationships.length.toLocaleString()} edges are intentionally separated into 177 reviewed substantive connections and 2,049 rule-derived navigation connections. Rule-derived edges support discovery and game mechanics but do not masquerade as causal, historical, or evidentiary claims. Sourced assertions identify their source records, and validation reports zero semantic or citation failures.

## Working systems

- Divination supports deterministic seeds, all twelve spreads, orientation states, filters, and pool disclosures.
- Twelve games include executable declarative reducers, resources, tracks, zones, legal ranges, and reachable win and loss fixtures.
- Story, character, world, faction, quest, and cosmology generators use canonical slot contracts, deterministic seeds, filters, and graph guidance.
- The local reference site includes Home, Tiles, Graph, Lineages, Divination, Spreads, Games, Story Lab, Research, Visual System, and Downloads views.
- Card drawers expose evidence states, claims, safety boundaries, sources, and typed relationships. Edge drawers expose rationale and provenance.

The release validator exercises 10,000 divination runs, 10,000 six-kind generator runs, and 10,000 reducer-backed game simulations. See \`exports/VALIDATION-REPORT.md\` and \`exports/validation-report.json\` for the final gate and deterministic checksums.

## Visual archive

The reference set contains ${referenceManifest.length} original, text-free enamel Tiles. Every subject is supplied as SVG plus 2048, 1024, 512, and 256 PNG. A generated flagship cover establishes the luminous enamel, cyan-signal, and restrained-gold direction. The production rules preserve readable silhouettes and keep labels in accessible HTML rather than baking text into artwork.

## Site quality review

The release was exercised in a browser at 1440×900 and 390×844. Tested journeys include card search and inspection, graph focus and edge provenance, seeded divination repeatability, a six-position spread, all six generators, game-rule inspection, research disclosure, lineages, visual assets, and downloads. The tested views produced no console errors, broken images, or page-level horizontal overflow. Offline operation is structurally self-contained: scripts, styles, data, artwork, and downloads use relative local paths; there are no CDN, account, tracker, fetch, or network dependencies. The executable \`OPEN-ORACLE.command\` opens \`site/index.html\` directly.

## Source boundary

The requested *Future Esoteric: The Unseen Realms* corpus was not present among the received files. It was not cited, summarized, or treated as evidence. This limitation is disclosed in the Research view and documented in [FUTURE-ESOTERIC-SOURCE-NOTE.md](../research/FUTURE-ESOTERIC-SOURCE-NOTE.md). A verified copy can be registered later without pretending that it influenced this release.

## Maintenance

Authored content, systems, site templates, and validation rules live under \`scripts/\` and \`site-source/\`. Rebuild with \`npm run generate\`, then run the complete release gate with \`npm run validate\`. Generated-file hashes live in \`manifest.json\`; the validator writes a second release-wide manifest after all checks pass.
`);
  console.log(JSON.stringify({cards:cards.length,candidates:longlist.length,relationships:systems.relationships.length,lineages:systems.lineages.length,spreads:systems.spreads.length,games:systems.games.length,references:referenceManifest.length,files:fileManifest.length},null,2));
}

await generate();
