// Which families answer which section kind — as a per-track CHOICE rather than
// as a constant.
//
// This was a module-level table in LookGenerator, identical for every track ever
// generated: an intro was always minimal/flow/organic, a drop always
// geometric/glitch/structural, and intro and outro were literally the same list.
// Every video therefore made the same genre decisions before a single seeded
// draw happened, which is a large source of cross-track sameness hiding inside
// something that looks like configuration.
//
// Measured, the cost was concrete: across twelve tracks, twenty-nine of
// forty-two scenes were cast in none of them. The library was not too small —
// most of it was unreachable.
//
// A director is one coherent point of view about what a song looks like. Not a
// shuffle of families: each mapping is internally consistent, and the ordering
// within a kind matters because the first family is weighted three times the
// third. Two tracks with different directors disagree about what a drop IS,
// which is the level at which videos should differ.

/**
 * Families a QUIET section is allowed to draw from, whatever the director
 * thinks. Phase 7 has enforced this since minimal existed, and the first draft
 * of this file broke it — `corrupt` opened on glitch and `geometer` opened on
 * geometric, on the theory that a point of view should apply everywhere.
 *
 * It should not. An intro that opens on a strobing scene and a breakdown that
 * answers a lull with a dense pattern are not bold, they are the two specific
 * mistakes the family coupling was introduced to prevent, and a viewer meets
 * them within fifteen seconds of pressing play. So intro, breakdown and outro
 * are off limits to the loud families for every director.
 *
 * The identity lives in build, drop and sustain — half the kinds, and the half
 * anyone remembers — plus which of the restful families a director leads with
 * when it is being quiet.
 */
export const RESTFUL_FAMILIES = ['minimal', 'flow', 'organic'];
const QUIET_KINDS = ['intro', 'breakdown', 'outro'];

/**
 * Each director maps every section kind to three families, most-preferred
 * first. Every kind must be present — a missing one silently falls back to the
 * whole library and the point of view is lost exactly where it matters most.
 */
export const DIRECTORS = [
    {
        name: 'ambient',
        // The original table. A drop resolves into geometry; everything quiet is
        // minimal. Still the most broadly applicable, so it keeps the most weight.
        weight: 3,
        families: {
            intro:     ['minimal', 'flow', 'organic'],
            build:     ['structural', 'geometric', 'flow'],
            drop:      ['geometric', 'glitch', 'structural'],
            sustain:   ['organic', 'flow', 'geometric'],
            breakdown: ['minimal', 'organic', 'flow'],
            outro:     ['minimal', 'flow', 'organic'],
        },
    },
    {
        name: 'brutalist',
        // Everything is architecture. Quiet means empty rather than soft, so it
        // leads on minimal and reaches for organic last.
        weight: 2,
        families: {
            intro:     ['minimal', 'organic', 'flow'],
            build:     ['structural', 'geometric', 'glitch'],
            drop:      ['structural', 'glitch', 'geometric'],
            sustain:   ['structural', 'geometric', 'flow'],
            breakdown: ['minimal', 'flow', 'organic'],
            outro:     ['minimal', 'organic', 'flow'],
        },
    },
    {
        name: 'organicist',
        // Nothing is ever built; things grow and dissolve. Deliberately never
        // reaches for glitch — a point of view is defined by what it refuses.
        weight: 2,
        families: {
            intro:     ['organic', 'flow', 'minimal'],
            build:     ['organic', 'flow', 'structural'],
            drop:      ['organic', 'geometric', 'flow'],
            sustain:   ['organic', 'flow', 'minimal'],
            breakdown: ['organic', 'minimal', 'flow'],
            outro:     ['flow', 'organic', 'minimal'],
        },
    },
    {
        name: 'corrupt',
        // The signal is damaged and the damage is the subject — everywhere the
        // damage is allowed to be. Its quiet sections lead on flow, so the calm
        // reads as signal drifting rather than as rest.
        weight: 2,
        families: {
            intro:     ['flow', 'minimal', 'organic'],
            build:     ['glitch', 'structural', 'geometric'],
            drop:      ['glitch', 'geometric', 'structural'],
            sustain:   ['glitch', 'organic', 'flow'],
            breakdown: ['minimal', 'flow', 'organic'],
            outro:     ['flow', 'minimal', 'organic'],
        },
    },
    {
        name: 'geometer',
        // Pattern first, everywhere, at every energy. The drop is not an
        // explosion, it is the pattern at its densest.
        weight: 2,
        families: {
            intro:     ['minimal', 'flow', 'organic'],
            build:     ['geometric', 'structural', 'flow'],
            drop:      ['geometric', 'glitch', 'structural'],
            sustain:   ['geometric', 'organic', 'flow'],
            breakdown: ['minimal', 'organic', 'flow'],
            outro:     ['minimal', 'flow', 'organic'],
        },
    },
];

export const DIRECTOR_NAMES = DIRECTORS.map((d) => d.name);

/**
 * Cast the director for a track.
 *
 * The audio tilts the odds and never decides: a noisy, loud track is more
 * likely to be filmed as corrupt or brutalist and a tonal one as organicist,
 * but every director stays reachable for every track. Predictable mapping from
 * measured features to look is the failure mode this whole layer exists to
 * avoid — it is how a library ends up with one house style per genre.
 */
export function pickDirector(summary, rng) {
    const noisy = Math.min(1, (summary.meanFlatness ?? 0.2) * 3);
    const bright = summary.meanCentroid ?? 0.5;

    const weights = DIRECTORS.map((d) => {
        let w = d.weight;
        if (d.name === 'corrupt')    w *= 0.5 + noisy * 2.0;
        if (d.name === 'brutalist')  w *= 0.6 + noisy * 1.2;
        if (d.name === 'organicist') w *= 0.6 + (1 - noisy) * 1.4;
        if (d.name === 'geometer')   w *= 0.7 + bright * 1.0;
        return w;
    });

    return rng.pickWeighted(DIRECTORS, weights);
}

export function directorByName(name) {
    return DIRECTORS.find((d) => d.name === name) || DIRECTORS[0];
}
