// Story Weaver — Auto-Weave behavior data.
// Drives the staged pipeline that emulates the semantic engine turning raw
// events into a multi-event narrative ("Playbook"). Pure data: stages, the
// candidate playbooks the engine surfaces, the signals it reasons over, the
// streamed draft (with citations back to events), and the API surface each
// stage maps to — so this reads as both a UX and an endpoint spec.

(function () {
const D = LMTData;
const ev = (id) => D.events.find((e) => e.id === id);

// ── Scatter layout (pre-cluster) — stable % positions for the 12 anchor events.
const SCATTER = {
  e01: { x: 18, y: 30 }, e02: { x: 41, y: 17 }, e03: { x: 29, y: 55 },
  e04: { x: 63, y: 22 }, e05: { x: 49, y: 63 }, e06: { x: 75, y: 45 },
  e07: { x: 23, y: 78 }, e08: { x: 45, y: 39 }, e09: { x: 67, y: 67 },
  e10: { x: 85, y: 31 }, e11: { x: 57, y: 83 }, e12: { x: 81, y: 76 },
};

// ── Candidate playbooks the Weaver auto-detects from the same event pool. ────
// Each is a *different* story the engine could form — the user reviews & accepts
// one. `h` on each beat is its height on the tension arc (top %, lower = higher).
const CANDIDATES = [
  {
    id: 'cand_snow',
    pid: 'pb_19c4',
    title: 'The year the snow came early',
    blurb: 'Distance and reprieve — the people you love, far and near, across one season.',
    threads: ['Lin & me', 'Family', 'Nights', 'N. & me'],
    confidence: 0.91,
    span: 'a season · 2–88d',
    beats: [
      { id: 'e01', role: 'Opening',   h: 58, note: 'The distance, the warmth of the call.' },
      { id: 'e03', role: 'Turn',      h: 39, note: 'Fear, then relief — the emotional spine.' },
      { id: 'e08', role: 'Gathering', h: 26, note: 'Everyone in one place. The opposite of distance.' },
      { id: 'e11', role: 'Wonder',    h: 31, note: 'A small awe between the heavy beats.' },
      { id: 'e09', role: 'Undertow',  h: 63, note: 'A quiet low keeps it honest.' },
      { id: 'e05', role: 'Close',     h: 47, note: 'Unresolved, but real — end on the building.' },
    ],
    signals: [
      { icon: 'People',   label: 'Recurring people', detail: 'Lin, Vera & N. thread through 5 of 6 memories', weight: 0.93 },
      { icon: 'Heart',    label: 'Emotional contour', detail: 'Relief → awe → grief — a real story shape', weight: 0.88 },
      { icon: 'Sparkles', label: 'Shared theme',      detail: '“distance & reprieve” surfaces in every entry', weight: 0.90 },
      { icon: 'Clock',    label: 'Time window',       detail: 'One continuous season, no large gaps', weight: 0.71 },
    ],
    excluded: [
      { id: 'e10', reason: 'Work decision — no tie to the emotional thread' },
      { id: 'e02', reason: 'Administrative; low narrative weight' },
      { id: 'e12', reason: 'Stronger fit in a separate recovery thread' },
    ],
    // Streamed draft — each fragment cites the beat (event) it was drawn from.
    draft: [
      { beat: 'e01', text: 'The snow came early to Kyoto that year, and Lin called to tell me about it — her voice small across the distance, delighted by the first white dusting on Higashiyama.' },
      { beat: 'e03', text: 'Then the call I had been dreading turned, impossibly, to relief: the biopsy came back clean, and the fear let go of me all at once.' },
      { beat: 'e08', text: 'By spring we were all at the lake for the seventieth — the whole noisy gathering of us, the exact opposite of distance.' },
      { beat: 'e11', text: 'One clear night we climbed to the roof and found the comet still faintly there, and for a moment nothing was far away at all.' },
      { beat: 'e09', text: 'Not every day held. There was a morning on the train when I wept for no reason I could name, and let it pass through me.' },
      { beat: 'e05', text: 'And there was the fight — the first real one in months. I end here, not because it resolved, but because it hadn’t, and that was the truest thing about the year.' },
    ],
  },
  {
    id: 'cand_run',
    pid: 'pb_22a7',
    title: 'Learning to run again',
    blurb: 'A quieter recovery arc — the body coming back, one ordinary motion at a time.',
    threads: ['Body', 'Quiet · me', 'Theo · weekly'],
    confidence: 0.78,
    span: 'recovery · 6–120d',
    beats: [
      { id: 'e09', role: 'Opening',   h: 60, note: 'The low point — stillness, no reason.' },
      { id: 'e12', role: 'Turn',      h: 40, note: 'The first run after the surgery.' },
      { id: 'e04', role: 'Rhythm',    h: 33, note: 'Movement becomes routine again.' },
      { id: 'e07', role: 'Renewal',   h: 30, note: 'Something kept alive through winter.' },
      { id: 'e03', role: 'Close',     h: 44, note: 'Relief — the body, and the family, intact.' },
    ],
    signals: [
      { icon: 'Heart',    label: 'Recovery theme', detail: 'Body, healing & renewal recur across the set', weight: 0.84 },
      { icon: 'Clock',    label: 'Long timeline',  detail: 'Spans 120 days — a slow, real arc', weight: 0.66 },
      { icon: 'Sparkles', label: 'Motion motif',   detail: 'walking, running, repotting — bodies in motion', weight: 0.74 },
    ],
    excluded: [
      { id: 'e08', reason: 'Belongs to the family-gathering thread' },
      { id: 'e10', reason: 'Work; unrelated to the body arc' },
    ],
    draft: [
      { beat: 'e09', text: 'It started, if it started anywhere, on the train — that morning I wept without a reason and understood something had gone quiet in me.' },
      { beat: 'e12', text: 'The first run back was almost nothing: a slow loop, the surgery still a rumor in the knee, and yet the body remembered how.' },
      { beat: 'e03', text: 'By the time the good news came — clean, clear, kept — I was moving again, and the two reliefs felt like one.' },
    ],
  },
  {
    id: 'cand_work',
    pid: null,
    title: 'A working year',
    blurb: 'A possible work thread — but too few linked memories to weave well yet.',
    threads: ['Work · 2026'],
    confidence: 0.52,
    belowThreshold: true,
    span: 'sparse · 4–71d',
    beats: [
      { id: 'e10', role: '—', h: 46, note: 'Quit the agency contract.' },
      { id: 'e02', role: '—', h: 42, note: 'Submitted the residency application.' },
    ],
    signals: [
      { icon: 'Sparkles', label: 'Weak cohesion', detail: 'Only a loose work theme connects these', weight: 0.52 },
      { icon: 'Clock',    label: 'Large gaps',    detail: 'Months apart with nothing between', weight: 0.34 },
    ],
    excluded: [],
    note: 'Only 2 linked memories — below the 0.6 weave threshold. The Weaver will keep collecting before it proposes this one.',
  },
];

// ── The pipeline stages. Each maps to the API endpoint(s) it would call. ─────
// `think` = engine monitor log lines (typed out). `api` = the endpoint spec.
const STAGES = [
  {
    key: 'inbox', label: 'Inbox', icon: 'Bell', verb: 'Watching for new memories',
    blurb: 'Memories captured since your last weave are queued, unwoven, embedded in the background.',
    think: ['watch: 32 unwoven memories', 'last weave · 9 days ago', 'idle — awaiting trigger'],
    api: [
      { m: 'GET', path: '/v1/events?status=unwoven&since={cursor}', note: 'New memories since the last weave', res: '{ "count": 32, "events": [ … ] }' },
    ],
  },
  {
    key: 'scan', label: 'Scan', icon: 'Sparkles', verb: 'Reading & embedding memories',
    blurb: 'Each memory is read and turned into a vector — its meaning, people, places, time and mood.',
    think: ['POST /weaver/scan → job wv_8f2a', 'embedding 32 memories…', 'extracting people · places · time · mood', 'vectors upserted → index'],
    api: [
      { m: 'POST', path: '/v1/weaver/scan', note: 'Start a weave job over unwoven memories', req: '{ "scope":"unwoven", "range":"180d" }', res: '{ "job_id":"wv_8f2a", "status":"running" }' },
      { m: 'POST', path: '/v1/embeddings', note: 'Vectorize each memory (text-embedding-3)', res: '{ "vectors": 32, "dim": 1536 }', sub: true },
      { m: 'GET',  path: '/v1/weaver/jobs/wv_8f2a/stream', note: 'SSE — progress + partial clusters', sub: true },
    ],
  },
  {
    key: 'cluster', label: 'Cluster', icon: 'Graph', verb: 'Linking memories into threads',
    blurb: 'Vector similarity plus community detection pull related memories together — and let the rest fall away.',
    think: ['cosine similarity over 32 vectors', 'community detection → 3 clusters', 'cl_1 cohesion 0.91 · 6 memories', 'pruning 6 low-relevance memories'],
    api: [
      { m: 'POST', path: '/v1/weaver/cluster', note: 'Similarity + community detection over the vectors', req: '{ "job_id":"wv_8f2a", "min_cohesion":0.6 }', res: '{ "clusters":[ { "id":"cl_1", "cohesion":0.91, "event_ids":[ … ] }, … ] }' },
    ],
  },
  {
    key: 'propose', label: 'Propose', icon: 'Branch', verb: 'Proposing a Playbook',
    blurb: 'The strongest thread is surfaced as a candidate Playbook, with its confidence, the signals behind it, and what it set aside. You accept, reshuffle, or dismiss.',
    think: ['rank candidates by confidence', 'cand_snow · 0.91 (accept-ready)', 'cand_run · 0.78', 'cand_work · 0.52 (below threshold)'],
    api: [
      { m: 'GET',  path: '/v1/weaver/candidates', note: 'Ranked candidate playbooks', res: '{ "candidates":[ { "id":"cand_snow", "confidence":0.91, … } ] }' },
      { m: 'POST', path: '/v1/playbooks', note: 'Accept → create the Playbook', req: '{ "title":"…", "event_ids":[ … ], "source":"weaver", "candidate_id":"cand_snow" }', res: '201 { "playbook_id":"pb_19c4" }' },
      { m: 'POST', path: '/v1/weaver/candidates/{id}/dismiss', note: 'Reject a candidate', sub: true },
    ],
  },
  {
    key: 'arc', label: 'Arc', icon: 'Compass', verb: 'Arranging the narrative arc',
    blurb: 'The reasoning model assigns each memory a role — opening, turn, close — and orders them into a shape with tension and release. Drag to overrule it.',
    think: ['POST /playbooks/pb_19c4/arc', 'assigning roles: Opening … Close', 'optimizing for tension + release', 'arc ready · 6 beats'],
    api: [
      { m: 'POST',  path: '/v1/playbooks/pb_19c4/arc', note: 'Reasoning model proposes ordered beats + roles', res: '{ "beats":[ { "event_id":"e01", "role":"Opening" }, … ] }' },
      { m: 'PATCH', path: '/v1/playbooks/pb_19c4/arc', note: 'Persist a manual reorder', req: '{ "order":["e01","e03","e08", … ] }', sub: true },
    ],
  },
  {
    key: 'draft', label: 'Draft', icon: 'Pen', verb: 'Drafting the prose',
    blurb: 'Prose streams in beat by beat. Every sentence traces to the memory it was drawn from — nothing is invented.',
    think: ['POST /playbooks/pb_19c4/draft (SSE)', 'streaming tokens…', 'emit citation → e03', 'grounded · 6 / 6 beats cited'],
    api: [
      { m: 'POST', path: '/v1/playbooks/pb_19c4/draft', note: 'SSE — stream prose with citations', req: '{ "tone":"literary", "length":"chapter" }', res: 'event: token · event: citation { "event_id":"e03", "span":[ … ] }' },
      { m: 'GET',  path: '/v1/events/e03', note: 'Resolve a citation back to its memory', sub: true },
    ],
  },
  {
    key: 'formed', label: 'Formed', icon: 'Check', verb: 'Playbook formed',
    blurb: 'The Playbook is now a saved entity — a chapter woven from your memories, ready to publish or keep private.',
    think: ['playbook pb_19c4 persisted', '6 events · 4 scenes linked', 'status → published', 'done.'],
    api: [
      { m: 'PATCH', path: '/v1/playbooks/pb_19c4', note: 'Publish the chapter', req: '{ "status":"published", "visibility":"scene" }', res: '200 { "status":"published" }' },
    ],
  },
];

window.WEAVER = { SCATTER, CANDIDATES, STAGES, ev };
})();
