// Which scenes have migrated to the identity artifacts, and what each of the
// rest needs. See MIGRATION.md for the recipe this reports progress against.
//
// Sixty-one scenes is too many to migrate from memory or from taste. The
// classifier below reads each shader and proposes a TIER from evidence in the
// source, so the work is a queue rather than a judgement call each time, and so
// two passes over the library reach the same answer.
//
// It is deliberately conservative: it proposes, the gate disposes. A scene is
// only migrated when `consumes` is declared AND checks.html?scene=<name> shows
// the matching `consumes:` line passing, which no amount of pattern matching
// can fake.
//
//   node tools/migration-status.js            progress plus the next ten
//   node tools/migration-status.js --all      every scene
//   node tools/migration-status.js --tier drawn

import { scenes } from '../src/scenes/registry.js';

const has = (n) => process.argv.includes(`--${n}`);
const argOf = (n) => {
    const i = process.argv.indexOf(`--${n}`);
    return i >= 0 ? process.argv[i + 1] : null;
};

/**
 * Tiers, in migration order. The order is by payoff per unit of risk: scenes
 * that already loop over discrete elements are a near-mechanical change and are
 * where the cast actually shows, while a full-frame field has no elements to
 * replace and can only take the ink.
 */
const TIERS = {
    drawn: {
        consumes: ['cast', 'ink', 'staging'],
        why: 'loops over discrete elements — replace the primitive with the cast, '
           + 'the placement with stageNode, the edge with inkMask',
    },
    figure: {
        consumes: ['cast', 'ink'],
        why: 'draws one or a few forms without a lattice — take the cast and the ink, '
           + 'keep its own composition',
    },
    field: {
        consumes: ['ink'],
        why: 'a continuous surface with no elements — inkValue on the way out, '
           + 'inkPattern where it already dithers or hatches',
    },
    treatment: {
        consumes: ['ink'],
        why: 'an effect over an image rather than an image — takes the value '
           + 'structure only, and should probably become an EFFECT rather than a scene',
    },
};

const RX = {
    migrated: /\b(castMain|castChorus|castSDF|inkMask|inkValue|stageNode)\s*\(/,
    // A loop whose body places something at a computed position: the signature
    // of an element-placing scene.
    elementLoop: /for\s*\(\s*int\s+\w+\s*=\s*0[\s\S]{0,900}?(length\s*\(\s*p\s*-|p\s*-\s*(pos|centre|center|c)\b|sigForm\s*\(|smoothstep\s*\([^)]*\bd\b)/,
    anyLoop: /for\s*\(\s*int\s+\w+\s*=\s*0/,
    // A single distance-field subject, no loop needed.
    figure: /\bsig(Shape|Form)\s*\(|\bsdf\w*\s*\(|length\s*\(\s*p\s*\)\s*-/,
    // Screen-space corruption: it operates on the frame, not on a world.
    treatment: /\bprev\s*\(|\bvUv\b[\s\S]{0,200}(tear|glitch|shift)|u_prev/,
    field: /\b(fbm|vnoise|curl|voronoi|noise)\s*\(/,
};

function classify(module) {
    const src = module.shader || '';
    if (RX.migrated.test(src) || (module.consumes || []).length) return 'migrated';
    if (module.role === 'accent') return 'accent';
    if (RX.elementLoop.test(src)) return 'drawn';
    if (RX.treatment.test(src)) return 'treatment';
    if (RX.figure.test(src) && !RX.anyLoop.test(src)) return 'figure';
    if (RX.field.test(src)) return 'field';
    return 'figure';
}

const rows = scenes.map((m) => ({
    name: m.name,
    family: m.family,
    role: m.role || 'stage',
    tier: classify(m),
    consumes: m.consumes || [],
    lines: (m.shader || '').split('\n').length,
}));

const migrated = rows.filter((r) => r.tier === 'migrated');
const pending = rows.filter((r) => r.tier !== 'migrated' && r.tier !== 'accent');
const accents = rows.filter((r) => r.tier === 'accent');

const bar = (v, w = 28) => '█'.repeat(Math.round(v * w)) + '·'.repeat(w - Math.round(v * w));

console.log(`\nMIGRATION STATUS — ${migrated.length}/${migrated.length + pending.length} scenes on the identity artifacts\n`);
console.log(`  ${bar(migrated.length / Math.max(1, migrated.length + pending.length))}  ` +
    `${(migrated.length / Math.max(1, migrated.length + pending.length) * 100).toFixed(0)}%` +
    `   (${accents.length} accents excluded — they are depth passes, not subjects)\n`);

console.log('  REMAINING BY TIER\n');
for (const [tier, spec] of Object.entries(TIERS)) {
    const group = pending.filter((r) => r.tier === tier);
    if (!group.length) continue;
    console.log(`  ${tier} — ${group.length} scenes → consumes: [${spec.consumes.join(', ')}]`);
    console.log(`      ${spec.why}`);
    console.log(`      ${group.map((r) => r.name).join(', ')}`);
    console.log('');
}

if (has('all') || argOf('tier')) {
    const want = argOf('tier');
    console.log('  PER SCENE\n');
    for (const r of rows.filter((x) => !want || x.tier === want)) {
        console.log(`  ${r.name.padEnd(24)} ${r.family.padEnd(11)} ${r.tier.padEnd(10)}` +
            `${r.consumes.length ? `[${r.consumes.join(',')}]` : ''}`);
    }
    console.log('');
} else if (pending.length) {
    // The queue: drawn first, biggest payoff and least invention required.
    const order = ['drawn', 'figure', 'field', 'treatment'];
    const next = pending.slice().sort(
        (a, b) => order.indexOf(a.tier) - order.indexOf(b.tier) || a.lines - b.lines);
    console.log('  NEXT TEN\n');
    for (const r of next.slice(0, 10)) {
        console.log(`  ${r.name.padEnd(24)} ${r.tier.padEnd(10)} ${r.family.padEnd(11)} ${r.lines} shader lines`);
    }
    console.log('');
    console.log('  Recipe: MIGRATION.md. Verify one with:');
    console.log(`      checks.html?scene=${encodeURIComponent(next[0].name)}`);
    console.log('  and look for a passing "consumes:" line for every artifact declared.\n');
}
