// SF Extension — workflow-projections unit tests
// Tests the pure rendering functions (no DB required).

import test from 'node:test';
import assert from 'node:assert/strict';
import { renderPlanContent, renderRoadmapContent } from '../workflow-projections.ts';
import type { MilestoneRow, SliceRow, TaskRow } from '../sf-db.ts';

// ─── Test fixtures ────────────────────────────────────────────────────────

function makeSlice(overrides: Partial<SliceRow> = {}): SliceRow {
  return {
    id: 'S01',
    milestone_id: 'M001',
    title: 'Auth Layer',
    status: 'active',
    risk: 'high',
    depends: [],
    demo: 'Login flow works end-to-end',
    goal: 'Implement JWT authentication',
    full_summary_md: '',
    full_uat_md: '',
    success_criteria: '',
    proof_level: '',
    integration_closure: '',
    observability_impact: '',
    adversarial_partner: '',
    adversarial_combatant: '',
    adversarial_architect: '',
    planning_meeting: null,
    created_at: '2026-01-01T00:00:00Z',
    completed_at: null,
    sequence: 1,
    replan_triggered_at: null,
    ...overrides,
  };
}

function makeMilestone(overrides: Partial<MilestoneRow> = {}): MilestoneRow {
  return {
    id: 'M001',
    title: 'Auth Foundation',
    status: 'active',
    depends_on: [],
    created_at: '2026-01-01T00:00:00Z',
    completed_at: null,
    vision: 'Ship the authentication foundation.',
    success_criteria: [],
    key_risks: [],
    proof_strategy: [],
    verification_contract: '',
    verification_integration: '',
    verification_operational: '',
    verification_uat: '',
    definition_of_done: [],
    requirement_coverage: '',
    boundary_map_markdown: '',
    vision_meeting: null,
    ...overrides,
  };
}

function makeTask(overrides: Partial<TaskRow> = {}): TaskRow {
  return {
    id: 'T01',
    slice_id: 'S01',
    milestone_id: 'M001',
    title: 'Create JWT middleware',
    status: 'pending',
    description: 'Implement JWT validation middleware',
    estimate: '2h',
    files: ['src/middleware/auth.ts'],
    verify: 'npm test src/middleware/auth.test.ts',
    one_liner: '',
    narrative: '',
    verification_result: '',
    duration: '',
    completed_at: null,
    blocker_discovered: false,
    deviations: '',
    known_issues: '',
    key_files: [],
    key_decisions: [],
    full_summary_md: '',
    full_plan_md: '',
    inputs: [],
    expected_output: [],
    observability_impact: '',
    sequence: 1,
    ...overrides,
  };
}

// ─── renderPlanContent: structure ────────────────────────────────────────

test('workflow-projections: renderPlanContent starts with H1 containing slice id and title', () => {
  const content = renderPlanContent(makeSlice(), []);
  assert.ok(content.startsWith('# S01: Auth Layer'), `expected H1, got: ${content.slice(0, 60)}`);
});

test('workflow-projections: renderPlanContent includes Goal line', () => {
  const content = renderPlanContent(makeSlice(), []);
  assert.ok(content.includes('**Goal:** Implement JWT authentication'));
});

test('workflow-projections: renderPlanContent includes Demo line', () => {
  const content = renderPlanContent(makeSlice(), []);
  assert.ok(content.includes('**Demo:** After this: Login flow works end-to-end'));
});

test('workflow-projections: renderPlanContent falls back to TBD when goal and full_summary_md are empty', () => {
  const slice = makeSlice({ goal: '', full_summary_md: '' });
  const content = renderPlanContent(slice, []);
  assert.ok(content.includes('**Goal:** TBD'));
});

test('workflow-projections: renderPlanContent falls back to TBD when goal is empty (full_summary_md ignored #2945)', () => {
  const slice = makeSlice({ goal: '', full_summary_md: 'Fallback goal text' });
  const content = renderPlanContent(slice, []);
  // #2945: full_summary_md is no longer used as a fallback — it contains
  // multi-line rendered markdown that corrupts single-line fields.
  assert.ok(content.includes('**Goal:** TBD'), `expected TBD fallback, got: ${content}`);
});

test('workflow-projections: renderPlanContent includes ## Tasks section', () => {
  const content = renderPlanContent(makeSlice(), []);
  assert.ok(content.includes('## Tasks'));
});

test('workflow-projections: renderPlanContent includes adversarial review and planning meeting sections', () => {
  const slice = makeSlice({
    adversarial_partner: 'The slice is narrowly scoped and grounded in real boundaries.',
    adversarial_combatant: 'The main risk is dropping ceremony state during persistence.',
    adversarial_architect: 'The plan must survive DB, projection, and restore paths.',
    planning_meeting: {
      trigger: 'There was still ambiguity after the first pass.',
      pm: 'Keep the slice focused on state persistence.',
      researcher: 'The surrounding workflow already reads these sections back.',
      partner: 'The current shape is enough to move forward.',
      combatant: 'Only if the projection output preserves the route honestly.',
      architect: 'Projection output is part of the workflow contract.',
      moderator: 'Proceed, but keep the route explicit.',
      recommendedRoute: 'planning',
      confidenceSummary: 'High confidence after projection coverage.',
    },
  });

  const content = renderPlanContent(slice, []);
  assert.ok(content.includes('## Adversarial Review'));
  assert.ok(content.includes('## Planning Meeting'));
  assert.ok(content.includes('### Recommended Route'));
  assert.ok(content.includes('planning'));
  assert.ok(content.includes('High confidence after projection coverage.'));
});

test('workflow-projections: renderRoadmapContent includes vision alignment meeting sections', () => {
  const milestone = makeMilestone({
    vision_meeting: {
      trigger: 'Need top-level roadmap agreement before execution.',
      pm: 'Keep the milestone narrow.',
      userAdvocate: 'Users need a roadmap that stays coherent.',
      customerPanel: 'Operators want confidence before execution.',
      business: 'The roadmap should be defensible.',
      researcher: 'Comparable systems preserve this reasoning explicitly.',
      deliveryLead: 'Sequence the smallest credible slice first.',
      partner: 'The current roadmap is viable.',
      combatant: 'Do not overstate confidence.',
      architect: 'Persist the moderation context with the roadmap.',
      moderator: 'Weighted synthesis says the roadmap is ready.',
      weightedSynthesis: 'This roadmap is narrow enough to execute.',
      confidenceByArea: '- sequencing: high',
      recommendedRoute: 'planning',
    },
  });

  const content = renderRoadmapContent(milestone, [makeSlice()]);
  assert.ok(content.includes('## Vision Alignment Meeting'));
  assert.ok(content.includes('### Weighted Synthesis'));
  assert.ok(content.includes('### Confidence By Area'));
  assert.ok(content.includes('### Recommended Route'));
  assert.ok(content.includes('planning'));
});

// ─── renderPlanContent: task checkboxes ──────────────────────────────────

test('workflow-projections: pending task renders with [ ] checkbox', () => {
  const task = makeTask({ status: 'pending' });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(content.includes('- [ ] **T01:'), `expected unchecked, got: ${content}`);
});

test('workflow-projections: done task renders with [x] checkbox', () => {
  const task = makeTask({ status: 'done' });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(content.includes('- [x] **T01:'), `expected checked, got: ${content}`);
});

test('workflow-projections: complete status renders with [x] checkbox', () => {
  const task = makeTask({ status: 'complete' }); // 'complete' and 'done' both → checked
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(content.includes('- [x] **T01:'));
});

// ─── renderPlanContent: task sublines ────────────────────────────────────

test('workflow-projections: task with estimate renders Estimate subline', () => {
  const task = makeTask({ estimate: '2h' });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(content.includes('  - Estimate: 2h'));
});

test('workflow-projections: task with empty estimate omits Estimate subline', () => {
  const task = makeTask({ estimate: '' });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(!content.includes('  - Estimate:'));
});

test('workflow-projections: task with files renders Files subline', () => {
  const task = makeTask({ files: ['src/auth.ts', 'src/auth.test.ts'] });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(content.includes('  - Files: src/auth.ts, src/auth.test.ts'));
});

test('workflow-projections: task with empty files array omits Files subline', () => {
  const task = makeTask({ files: [] });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(!content.includes('  - Files:'));
});

test('workflow-projections: task with verify renders Verify subline', () => {
  const task = makeTask({ verify: 'npm test' });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(content.includes('  - Verify: npm test'));
});

test('workflow-projections: task with no verify omits Verify subline', () => {
  const task = makeTask({ verify: '' });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(!content.includes('  - Verify:'));
});

test('workflow-projections: task with duration renders Duration subline', () => {
  const task = makeTask({ duration: '45m' });
  const content = renderPlanContent(makeSlice(), [task]);
  assert.ok(content.includes('  - Duration: 45m'));
});

test('workflow-projections: multiple tasks rendered in order', () => {
  const t1 = makeTask({ id: 'T01', title: 'First task', sequence: 1 });
  const t2 = makeTask({ id: 'T02', title: 'Second task', sequence: 2 });
  const content = renderPlanContent(makeSlice(), [t1, t2]);
  const idxT1 = content.indexOf('**T01:');
  const idxT2 = content.indexOf('**T02:');
  assert.ok(idxT1 < idxT2, 'T01 should appear before T02');
});
