{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : Harmonic.Framework.Builder.Types
-- Description : Types for the harmonic generation engine
--
-- Data types and configuration for the Builder module.
-- Includes HarmonicContext, GeneratorConfig, ParsedContext,
-- and all diagnostic types.

module Harmonic.Framework.Builder.Types
  ( -- * Harmonic Context (R constraints)
    HarmonicContext(..)
  , harmonicContext
  , hContext

    -- * Context Modifiers
  , Drift(..)
  , hcOvertones
  , hcKey
  , hcRoots
  , dissonant
  , consonant
  , invSkip
  , hcPedal
  , hcTristrata

    -- * Configuration
  , GeneratorConfig(..)
  , defaultConfig

    -- * Pre-parsed Context
  , ParsedContext(..)
  , parseContextOnce
  , keySpellingOf

    -- * Bass Direction (re-exported from Filter)
  , BassDirection(..)
  , BassDirectionSpec(..)
  , BDKind(..)
  , BDSelector(..)

    -- * Generation Configuration (Modifier-Based API)
  , Verbosity(..)
  , GenConfig(..)
  , GenMode(..)

    -- * Diagnostics Types
  , TransformTrace(..)
  , AdvanceTrace(..)
  , StepDiagnostic(..)
  , FusionDiag(..)
  , GenerationDiagnostics(..)
  , AttemptDiagnostic(..)
  ) where

import           Data.List (intercalate)
import qualified Data.IntSet as IntSet
import qualified Data.Text as T
import           Data.Text (Text)

import qualified Harmonic.Rules.Types.Harmony as H
import qualified Harmonic.Rules.Types.Pitch as P
import qualified Harmonic.Rules.Types.Progression as Prog
import qualified Harmonic.Rules.Types.ProgressionContext as PC
import qualified Harmonic.Rules.Types.Scale as Sc
import qualified Harmonic.Evaluation.Scoring.Progression as PS
import           Harmonic.Rules.Constraints.Filter (parseOvertones', parseKey, isWildcard, resolveRoots,
                                                     BassDirection(..), BassDirectionSpec(..),
                                                     BDKind(..), BDSelector(..),
                                                     parseBassDirectionSpec, stripDirectionToken,
                                                     noteNameToPitchClass)

-------------------------------------------------------------------------------
-- Harmonic Context (R Constraints)
-------------------------------------------------------------------------------

-- |Harmonic context defines the Rules (R) that constrain the generative space.
--
-- These filters are applied BEFORE database evaluation (R in R→E→T pipeline),
-- limiting which cadences can even be considered as candidates.
--
-- Three-part filtering system:
--   * overtones: Pitch candidate set (e.g., "E A D G" for bass tuning overtones)
--   * key: Key filter applied to candidates (e.g., "C", "#", "bb" for key signature)
--   * roots: Root\/bass candidate set (e.g., "E F# G" for valid root notes)
--
-- Filters use "*" as wildcard (match all). Format matches legacy Overtone.hs notation.
data HarmonicContext = HarmonicContext
  { HarmonicContext -> Text
_hcOvertones        :: Text   -- ^ Filter by overtone content ("*" = all)
  , HarmonicContext -> Text
_hcKey              :: Text   -- ^ Filter by key signature ("C", "#", "bb", "*")
  , HarmonicContext -> Text
_hcRoots            :: Text   -- ^ Filter by root notes ("*" = all)
  , HarmonicContext -> Drift
_hcDrift            :: Drift  -- ^ Dissonance drift direction
  , HarmonicContext -> Int
_hcInversionSpacing :: Int    -- ^ Minimum non-inversions between inversions (default 0)
  , HarmonicContext -> Text
_hcPedal            :: Text   -- ^ Required\/preferred tones ("C G?", "" = no pedal)
  , HarmonicContext -> Text
_hcTristrata        :: Text   -- ^ Tristrata allow-list ("" = all 12; "5" = only #5; "1 2 5" = whitelist)
  } deriving (HarmonicContext -> HarmonicContext -> Bool
(HarmonicContext -> HarmonicContext -> Bool)
-> (HarmonicContext -> HarmonicContext -> Bool)
-> Eq HarmonicContext
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: HarmonicContext -> HarmonicContext -> Bool
== :: HarmonicContext -> HarmonicContext -> Bool
$c/= :: HarmonicContext -> HarmonicContext -> Bool
/= :: HarmonicContext -> HarmonicContext -> Bool
Eq)

instance Show HarmonicContext where
  show :: HarmonicContext -> String
show HarmonicContext
ctx = String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
" | " ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null)
    [ String
"overtones " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack (HarmonicContext -> Text
_hcOvertones HarmonicContext
ctx)
    , String
"key " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack (HarmonicContext -> Text
_hcKey HarmonicContext
ctx)
    , String
"roots " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack (HarmonicContext -> Text
_hcRoots HarmonicContext
ctx)
    , Drift -> String
forall {a}. IsString a => Drift -> a
driftStr (HarmonicContext -> Drift
_hcDrift HarmonicContext
ctx)
    , Int -> String
forall {a}. (Eq a, Num a, Show a) => a -> String
invStr (HarmonicContext -> Int
_hcInversionSpacing HarmonicContext
ctx)
    , Text -> String
pedalStr (HarmonicContext -> Text
_hcPedal HarmonicContext
ctx)
    ]
    where
      driftStr :: Drift -> a
driftStr Drift
Free      = a
""
      driftStr Drift
Dissonant = a
"drift dissonant"
      driftStr Drift
Consonant = a
"drift consonant"
      invStr :: a -> String
invStr a
0 = String
""
      invStr a
n = String
"inv skip " String -> ShowS
forall a. [a] -> [a] -> [a]
++ a -> String
forall a. Show a => a -> String
show a
n
      pedalStr :: Text -> String
pedalStr Text
t = if Text -> Bool
T.null (Text -> Text
T.strip Text
t) then String
"" else String
"pedal " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack Text
t

-- |Constructor for HarmonicContext.
--
-- Arguments:
--   * overtones: Pitch set filter ("E A D G", "C", "*")
--   * key: Key signature filter ("C", "#", "bb", "Am", "*")
--   * roots: Root notes filter ("E F# G", "1#", "*")
--
-- Example:
--   harmonicContext "*" "*" "*"       -- No filtering (all candidates)
--   harmonicContext "E A D G" "C" "*" -- Bass tuning, C major key
--   harmonicContext "*" "#" "E G"     -- G major key, E\/G roots only
harmonicContext :: Text -> Text -> Text -> HarmonicContext
harmonicContext :: Text -> Text -> Text -> HarmonicContext
harmonicContext Text
o Text
k Text
r = Text
-> Text -> Text -> Drift -> Int -> Text -> Text -> HarmonicContext
HarmonicContext Text
o Text
k Text
r Drift
Free Int
0 Text
"" Text
""

-- |Default harmonic context for Tidal live coding: all wildcards (chromatic).
-- Named 'hContext' to avoid collision with TidalCycles' EventF.context field.
--
-- Use modifier functions to constrain the context:
--
-- @
-- ctx = invSkip 2
--     $ consonant
--     $ hcRoots "C E G"
--     $ hcKey "0#"
--     $ hcOvertones "E A D G"
--     $ hContext
-- @
hContext :: HarmonicContext
hContext :: HarmonicContext
hContext = Text
-> Text -> Text -> Drift -> Int -> Text -> Text -> HarmonicContext
HarmonicContext Text
"*" Text
"*" Text
"*" Drift
Free Int
0 Text
"" Text
""

-------------------------------------------------------------------------------
-- Dissonance Drift
-------------------------------------------------------------------------------

-- |Direction of dissonance drift across a generated progression.
--
-- When applied to a HarmonicContext, the generation engine filters the
-- candidate pool at each step so that only chords with equal or greater
-- (Dissonant) or equal or lesser (Consonant) dissonance than the current
-- chord are preferred. Free imposes no constraint (default).
--
-- __Advisory, not hard__: if the drift predicate would empty the pool at a
-- step, the filter relaxes and that step proceeds unconstrained rather than
-- reaching an absorbing state ('Harmonic.Framework.Builder.Core.applyDriftFilter' in Builder.Core). Note the
-- predicate is 'Harmonic.Evaluation.Scoring.Dissonance.dissonanceScore' — an evaluation function acting as a
-- filter, the documented E-inside-R leak (see ARCHITECTURE §2).
data Drift = Dissonant | Consonant | Free deriving (Int -> Drift -> ShowS
[Drift] -> ShowS
Drift -> String
(Int -> Drift -> ShowS)
-> (Drift -> String) -> ([Drift] -> ShowS) -> Show Drift
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Drift -> ShowS
showsPrec :: Int -> Drift -> ShowS
$cshow :: Drift -> String
show :: Drift -> String
$cshowList :: [Drift] -> ShowS
showList :: [Drift] -> ShowS
Show, Drift -> Drift -> Bool
(Drift -> Drift -> Bool) -> (Drift -> Drift -> Bool) -> Eq Drift
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Drift -> Drift -> Bool
== :: Drift -> Drift -> Bool
$c/= :: Drift -> Drift -> Bool
/= :: Drift -> Drift -> Bool
Eq)

-- |Set overtone filter. Default: @"*"@ (all pitches).
--
-- @hcOvertones "E A D G" $ hContext@ — bass tuning overtones
hcOvertones :: String -> HarmonicContext -> HarmonicContext
hcOvertones :: String -> HarmonicContext -> HarmonicContext
hcOvertones String
o HarmonicContext
ctx = HarmonicContext
ctx { _hcOvertones = T.pack o }

-- |Set key filter. Default: @"*"@ (chromatic).
--
-- @hcKey "0#" $ hContext@ — C major
hcKey :: String -> HarmonicContext -> HarmonicContext
hcKey :: String -> HarmonicContext -> HarmonicContext
hcKey String
k HarmonicContext
ctx = HarmonicContext
ctx { _hcKey = T.pack k }

-- |Set roots\/bass filter. Default: @"*"@ (all roots).
--
-- @hcRoots "C E G" $ hContext@ — only C, E, G as bass notes
hcRoots :: String -> HarmonicContext -> HarmonicContext
hcRoots :: String -> HarmonicContext -> HarmonicContext
hcRoots String
r HarmonicContext
ctx = HarmonicContext
ctx { _hcRoots = T.pack r }

-- |Modify context to trend toward increasing dissonance.
-- Each subsequent chord should have dissonance >= the current chord;
-- advisory — relaxes at any step where it would empty the pool.
dissonant :: HarmonicContext -> HarmonicContext
dissonant :: HarmonicContext -> HarmonicContext
dissonant HarmonicContext
ctx = HarmonicContext
ctx { _hcDrift = Dissonant }

-- |Modify context to trend toward decreasing dissonance.
-- Each subsequent chord should have dissonance <= the current chord;
-- advisory — relaxes at any step where it would empty the pool.
consonant :: HarmonicContext -> HarmonicContext
consonant :: HarmonicContext -> HarmonicContext
consonant HarmonicContext
ctx = HarmonicContext
ctx { _hcDrift = Consonant }

-- |Set minimum number of non-inversion states between inversions.
--
-- @invSkip 0@ allows inversions at any step (default, current behaviour).
-- @invSkip 1@ requires at least 1 non-inversion between inversions.
-- @invSkip 2@ requires at least 2 non-inversions between inversions.
-- The starting state counts toward the counter (a non-inversion start
-- means the first generated step may already be an inversion with @invSkip 1@).
--
-- __Advisory, not hard__: if excluding inversions would empty the pool at a
-- step, the spacing constraint relaxes for that step rather than halting
-- generation.
invSkip :: Int -> HarmonicContext -> HarmonicContext
invSkip :: Int -> HarmonicContext -> HarmonicContext
invSkip Int
n HarmonicContext
ctx = HarmonicContext
ctx { _hcInversionSpacing = n }

-- |Require specific pitch classes to be present in every generated chord.
--
-- Tokens are note names (@"C"@, @"G#"@, @"Bb"@). A trailing @?@ marks a tone
-- as preferred rather than required — it is applied when it does not reduce
-- the candidate pool below a minimum viable size, and relaxed otherwise.
--
-- __Advisory at the limit__: the relaxation chain is preferred → required →
-- unfiltered (@applyPedalFilter@ in Builder.Core), so even /required/ tones
-- are dropped as a last resort at a step where enforcing them would leave no
-- candidates — generation never reaches an absorbing state through a pedal
-- constraint.
--
-- @hcPedal "C" $ hContext@       — C must appear in every chord
-- @hcPedal "C G" $ hContext@     — C and G must both appear
-- @hcPedal "C G?" $ hContext@    — C required, G preferred
hcPedal :: String -> HarmonicContext -> HarmonicContext
hcPedal :: String -> HarmonicContext -> HarmonicContext
hcPedal String
p HarmonicContext
ctx = HarmonicContext
ctx { _hcPedal = T.pack p }

-- |Restrict the active tristrata pool for 'Harmonic.Framework.Builder.genP'.
--
-- @""@ (default) — all 12 tristrata allowed.
-- @"5"@          — lock to a single tristrata (here #5, IV-VI-X).
-- @"1 2 5"@      — whitelist multiple tristrata.
-- @"[1,2,5]"@    — bracket form accepted.
--
-- Parsed via 'Sc.parseTristrataList'; unknown tokens are silently discarded.
hcTristrata :: String -> HarmonicContext -> HarmonicContext
hcTristrata :: String -> HarmonicContext -> HarmonicContext
hcTristrata String
t HarmonicContext
ctx = HarmonicContext
ctx { _hcTristrata = T.pack t }

-------------------------------------------------------------------------------
-- Generator Configuration
-------------------------------------------------------------------------------

-- |Configuration for the progression generator.
--
-- @gcQuad@ switches on the gen4 family: after each triad selection, the
-- step fuses one R-valid palette tone into the chord (4-note output) and
-- the walk continues from the fused chord's most-consonant embedded triad
-- (see 'Harmonic.Framework.Builder.Core.fuseState').
--
-- Historical note: the former @gcPoolSize@ field was removed (2026-08-19)
-- because no generation path ever read it — the candidate pool is
-- deliberately unlimited (full 660-candidate fallback; see
-- 'Harmonic.Framework.Builder.Core').
data GeneratorConfig = GeneratorConfig
  { GeneratorConfig -> Bool
gcQuad :: !Bool  -- ^ gen4: fuse a 4th tone into every generated bar (default False)
  } deriving (Int -> GeneratorConfig -> ShowS
[GeneratorConfig] -> ShowS
GeneratorConfig -> String
(Int -> GeneratorConfig -> ShowS)
-> (GeneratorConfig -> String)
-> ([GeneratorConfig] -> ShowS)
-> Show GeneratorConfig
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> GeneratorConfig -> ShowS
showsPrec :: Int -> GeneratorConfig -> ShowS
$cshow :: GeneratorConfig -> String
show :: GeneratorConfig -> String
$cshowList :: [GeneratorConfig] -> ShowS
showList :: [GeneratorConfig] -> ShowS
Show, GeneratorConfig -> GeneratorConfig -> Bool
(GeneratorConfig -> GeneratorConfig -> Bool)
-> (GeneratorConfig -> GeneratorConfig -> Bool)
-> Eq GeneratorConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: GeneratorConfig -> GeneratorConfig -> Bool
== :: GeneratorConfig -> GeneratorConfig -> Bool
$c/= :: GeneratorConfig -> GeneratorConfig -> Bool
/= :: GeneratorConfig -> GeneratorConfig -> Bool
Eq)

-- |Default configuration.
defaultConfig :: GeneratorConfig
defaultConfig :: GeneratorConfig
defaultConfig = GeneratorConfig { gcQuad :: Bool
gcQuad = Bool
False }

-- |Pre-parsed HarmonicContext for O(1) membership tests.
-- Computed once per generation run, avoiding repeated text parsing.
data ParsedContext = ParsedContext
  { ParsedContext -> IntSet
pcEffectiveOvertones :: !IntSet.IntSet  -- ^ Key-filtered overtone pitch classes
  , ParsedContext -> IntSet
pcAllowedBassNotes   :: !IntSet.IntSet  -- ^ Resolved root\/bass pitch classes
  , ParsedContext -> Bool
pcIsRootsWild        :: !Bool           -- ^ Whether roots filter is wildcard
  , ParsedContext -> Bool
pcIsKeyWild          :: !Bool           -- ^ Whether key filter is wildcard
  , ParsedContext -> Bool
pcIsOvertonesWild    :: !Bool           -- ^ Whether overtones filter is wildcard
  , ParsedContext -> [Int]
pcRawOvertones       :: ![Int]          -- ^ Raw overtone list (for fallback triad generation)
  , ParsedContext -> Maybe BassDirectionSpec
pcBassDirectionSpec  :: !(Maybe BassDirectionSpec)  -- ^ Rise\/fall bass direction spec (resolved per step)
  , ParsedContext -> Drift
pcDrift              :: !Drift                  -- ^ Dissonance drift direction
  , ParsedContext -> Int
pcInversionSpacing   :: !Int                    -- ^ Minimum non-inversions between inversions
  , ParsedContext -> IntSet
pcPedalRequired      :: !IntSet.IntSet  -- ^ Pitch classes that must be present in every chord
  , ParsedContext -> IntSet
pcPedalPreferred     :: !IntSet.IntSet  -- ^ Preferred pitch classes (relaxed if pool too small)
  , ParsedContext -> [Tristrata]
pcAllowedTristrata   :: ![Sc.Tristrata] -- ^ Tristrata allow-list ('Harmonic.Framework.Builder.genP' only; default = all 12)
  , ParsedContext -> Double
pcSoftBoost          :: !Double         -- ^ Multiplier applied to @badness@ at candidate-scoring time. Default 1.0 (no effect). 'Harmonic.Framework.Builder.genP' sets this per bar based on (s', t') continuity against the prior bar.
  , ParsedContext -> Bool
pcStrictContainment  :: !Bool           -- ^ When 'True', every absolute PC of a candidate cadence (including the bass) must be a member of 'pcEffectiveOvertones'. Default 'False' preserves the legacy bass-exemption behaviour for 'Harmonic.Framework.Builder.gen'. @runStrataGen@ sets 'True' per bar to enforce single-strata containment.
  , ParsedContext -> Maybe EnharmonicSpelling
pcKeySpelling        :: !(Maybe H.EnharmonicSpelling) -- ^ Enharmonic side implied by the declared key signature ('Nothing' for wildcard \/ 0-accidental \/ C). When present it overrides per-bar spelling inference — a five-flat context never prints F#.
  }

-- |Parse pedal tone string into required and preferred IntSets.
-- Tokens ending in @?@ are preferred; all others are required.
-- Invalid note names are silently ignored.
parsePedalTones :: Text -> (IntSet.IntSet, IntSet.IntSet)
parsePedalTones :: Text -> (IntSet, IntSet)
parsePedalTones Text
input
  | Text -> Bool
T.null (Text -> Text
T.strip Text
input) = (IntSet
IntSet.empty, IntSet
IntSet.empty)
  | Bool
otherwise =
      let tokens :: [Text]
tokens = Text -> [Text]
T.words Text
input
          classify :: Text -> (Bool, Maybe Int)
classify Text
t
            | Text -> Text -> Bool
T.isSuffixOf Text
"?" Text
t = (Bool
False, Text -> Maybe Int
noteNameToPitchClass (HasCallStack => Text -> Text
Text -> Text
T.init Text
t))
            | Bool
otherwise          = (Bool
True,  Text -> Maybe Int
noteNameToPitchClass Text
t)
          pairs :: [(Bool, Maybe Int)]
pairs = (Text -> (Bool, Maybe Int)) -> [Text] -> [(Bool, Maybe Int)]
forall a b. (a -> b) -> [a] -> [b]
map Text -> (Bool, Maybe Int)
classify [Text]
tokens
          required :: IntSet
required  = [Int] -> IntSet
IntSet.fromList [Int
pc | (Bool
True,  Just Int
pc) <- [(Bool, Maybe Int)]
pairs]
          preferred :: IntSet
preferred = [Int] -> IntSet
IntSet.fromList [Int
pc | (Bool
False, Just Int
pc) <- [(Bool, Maybe Int)]
pairs]
      in (IntSet
required, IntSet
preferred)

-- |Parse a HarmonicContext once into efficient lookup structures.
parseContextOnce :: HarmonicContext -> ParsedContext
parseContextOnce :: HarmonicContext -> ParsedContext
parseContextOnce HarmonicContext
ctx =
  let rawOvertones :: [Int]
rawOvertones = Int -> Text -> [Int]
parseOvertones' Int
3 (HarmonicContext -> Text
_hcOvertones HarmonicContext
ctx)
      keyPcs :: [Int]
keyPcs = Text -> [Int]
parseKey (HarmonicContext -> Text
_hcKey HarmonicContext
ctx)
      keyWild :: Bool
keyWild = Text -> Bool
isWildcard (HarmonicContext -> Text
_hcKey HarmonicContext
ctx)
      effectiveOvertones :: [Int]
effectiveOvertones = if Bool
keyWild
                           then [Int]
rawOvertones
                           else (Int -> Bool) -> [Int] -> [Int]
forall a. (a -> Bool) -> [a] -> [a]
filter (Int -> [Int] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Int]
keyPcs) [Int]
rawOvertones
      -- Strip direction token before resolving roots
      rootsRaw :: Text
rootsRaw = HarmonicContext -> Text
_hcRoots HarmonicContext
ctx
      rootsStripped :: Text
rootsStripped = Text -> Text
stripDirectionToken Text
rootsRaw
      bassDirSpec :: Maybe BassDirectionSpec
bassDirSpec = Text -> Maybe BassDirectionSpec
parseBassDirectionSpec Text
rootsRaw
      allowedBassNotes :: [Int]
allowedBassNotes = Text -> Text -> Text -> [Int]
resolveRoots (HarmonicContext -> Text
_hcOvertones HarmonicContext
ctx) (HarmonicContext -> Text
_hcKey HarmonicContext
ctx) Text
rootsStripped
      (IntSet
pedalReq, IntSet
pedalPref) = Text -> (IntSet, IntSet)
parsePedalTones (HarmonicContext -> Text
_hcPedal HarmonicContext
ctx)
      allowedTS :: [Tristrata]
allowedTS =
        let idxs :: [Int]
idxs = String -> [Int]
Sc.parseTristrataList (Text -> String
T.unpack (HarmonicContext -> Text
_hcTristrata HarmonicContext
ctx))
        in if [Int] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Int]
idxs
             then [Tristrata]
Sc.validTristrata
             else (Int -> Tristrata) -> [Int] -> [Tristrata]
forall a b. (a -> b) -> [a] -> [b]
map Int -> Tristrata
Sc.tristrataIndex [Int]
idxs
  in ParsedContext
    { pcEffectiveOvertones :: IntSet
pcEffectiveOvertones = [Int] -> IntSet
IntSet.fromList [Int]
effectiveOvertones
    , pcAllowedBassNotes :: IntSet
pcAllowedBassNotes   = [Int] -> IntSet
IntSet.fromList [Int]
allowedBassNotes
    , pcIsRootsWild :: Bool
pcIsRootsWild        = Text -> Bool
isWildcard Text
rootsStripped
    , pcIsKeyWild :: Bool
pcIsKeyWild          = Bool
keyWild
    , pcIsOvertonesWild :: Bool
pcIsOvertonesWild    = Text -> Bool
isWildcard (HarmonicContext -> Text
_hcOvertones HarmonicContext
ctx)
    , pcRawOvertones :: [Int]
pcRawOvertones       = [Int]
rawOvertones
    , pcBassDirectionSpec :: Maybe BassDirectionSpec
pcBassDirectionSpec  = Maybe BassDirectionSpec
bassDirSpec
    , pcDrift :: Drift
pcDrift              = HarmonicContext -> Drift
_hcDrift HarmonicContext
ctx
    , pcInversionSpacing :: Int
pcInversionSpacing   = HarmonicContext -> Int
_hcInversionSpacing HarmonicContext
ctx
    , pcPedalRequired :: IntSet
pcPedalRequired      = IntSet
pedalReq
    , pcPedalPreferred :: IntSet
pcPedalPreferred     = IntSet
pedalPref
    , pcAllowedTristrata :: [Tristrata]
pcAllowedTristrata   = [Tristrata]
allowedTS
    , pcSoftBoost :: Double
pcSoftBoost          = Double
1.0
    , pcStrictContainment :: Bool
pcStrictContainment  = Bool
False
    , pcKeySpelling :: Maybe EnharmonicSpelling
pcKeySpelling        = Text -> Maybe EnharmonicSpelling
keySpellingOf (HarmonicContext -> Text
_hcKey HarmonicContext
ctx)
    }

-- |Enharmonic side implied by a key-signature string. A key filter may
-- carry several tokens (their pitch sets union into the candidate pool),
-- so the side is a per-token vote: every token flat-side -> flat, every
-- token sharp-side -> sharp, mixed or indeterminate -> neutral (spelling
-- falls back to content inference and continuity). Count forms carry
-- their side directly ("2b" flat, "3#" sharp; zero-accidental forms are
-- neutral); note-name forms follow the circle of fifths (F and every
-- flat name -> flat; G, D, A, E, B and every sharp name -> sharp; C is
-- ambiguous -> neutral). Removal tokens ("-G") shape the pool, not the
-- spelling, and do not vote. Wildcards are neutral.
keySpellingOf :: Text -> Maybe H.EnharmonicSpelling
keySpellingOf :: Text -> Maybe EnharmonicSpelling
keySpellingOf Text
raw
  | Text -> Bool
isWildcard Text
raw = Maybe EnharmonicSpelling
forall a. Maybe a
Nothing
  | Bool
otherwise =
      let toks :: [Text]
toks  = [ Text
t | Text
t <- Text -> [Text]
T.words (Text -> Text
T.toLower (Text -> Text
T.strip Text
raw))
                      , Bool -> Bool
not (Text
"-" Text -> Text -> Bool
`T.isPrefixOf` Text
t) ]
          sides :: [EnharmonicSpelling]
sides = [ EnharmonicSpelling
s | Just EnharmonicSpelling
s <- (Text -> Maybe EnharmonicSpelling)
-> [Text] -> [Maybe EnharmonicSpelling]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Maybe EnharmonicSpelling
tokenSide [Text]
toks ]
      in case [EnharmonicSpelling]
sides of
           [] -> Maybe EnharmonicSpelling
forall a. Maybe a
Nothing
           [EnharmonicSpelling]
ss | (EnharmonicSpelling -> Bool) -> [EnharmonicSpelling] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (EnharmonicSpelling -> EnharmonicSpelling -> Bool
forall a. Eq a => a -> a -> Bool
== EnharmonicSpelling
H.FlatSpelling)  [EnharmonicSpelling]
ss -> EnharmonicSpelling -> Maybe EnharmonicSpelling
forall a. a -> Maybe a
Just EnharmonicSpelling
H.FlatSpelling
              | (EnharmonicSpelling -> Bool) -> [EnharmonicSpelling] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (EnharmonicSpelling -> EnharmonicSpelling -> Bool
forall a. Eq a => a -> a -> Bool
== EnharmonicSpelling
H.SharpSpelling) [EnharmonicSpelling]
ss -> EnharmonicSpelling -> Maybe EnharmonicSpelling
forall a. a -> Maybe a
Just EnharmonicSpelling
H.SharpSpelling
              | Bool
otherwise                   -> Maybe EnharmonicSpelling
forall a. Maybe a
Nothing
  where
    tokenSide :: Text -> Maybe EnharmonicSpelling
tokenSide Text
t
      | Text
t Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text
"0#", Text
"0b"]          = Maybe EnharmonicSpelling
forall a. Maybe a
Nothing
      | Text
"#" Text -> Text -> Bool
`T.isSuffixOf` Text
t           = EnharmonicSpelling -> Maybe EnharmonicSpelling
forall a. a -> Maybe a
Just EnharmonicSpelling
H.SharpSpelling
      | Text
t Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"b"                       = EnharmonicSpelling -> Maybe EnharmonicSpelling
forall a. a -> Maybe a
Just EnharmonicSpelling
H.SharpSpelling  -- the key of B
      | (Char -> Bool) -> Text -> Bool
T.any (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'b') Text
t               = EnharmonicSpelling -> Maybe EnharmonicSpelling
forall a. a -> Maybe a
Just EnharmonicSpelling
H.FlatSpelling
      | Text
t Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"f"                       = EnharmonicSpelling -> Maybe EnharmonicSpelling
forall a. a -> Maybe a
Just EnharmonicSpelling
H.FlatSpelling
      | Text
t Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text
"g", Text
"d", Text
"a", Text
"e"]  = EnharmonicSpelling -> Maybe EnharmonicSpelling
forall a. a -> Maybe a
Just EnharmonicSpelling
H.SharpSpelling
      | Bool
otherwise                      = Maybe EnharmonicSpelling
forall a. Maybe a
Nothing  -- "c", unrecognised

-------------------------------------------------------------------------------
-- Generation Configuration (Modifier-Based API)
-------------------------------------------------------------------------------

-- |Verbosity level for generation output.
data Verbosity = Silent | Standard | Verbose deriving (Int -> Verbosity -> ShowS
[Verbosity] -> ShowS
Verbosity -> String
(Int -> Verbosity -> ShowS)
-> (Verbosity -> String)
-> ([Verbosity] -> ShowS)
-> Show Verbosity
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Verbosity -> ShowS
showsPrec :: Int -> Verbosity -> ShowS
$cshow :: Verbosity -> String
show :: Verbosity -> String
$cshowList :: [Verbosity] -> ShowS
showList :: [Verbosity] -> ShowS
Show, Verbosity -> Verbosity -> Bool
(Verbosity -> Verbosity -> Bool)
-> (Verbosity -> Verbosity -> Bool) -> Eq Verbosity
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Verbosity -> Verbosity -> Bool
== :: Verbosity -> Verbosity -> Bool
$c/= :: Verbosity -> Verbosity -> Bool
/= :: Verbosity -> Verbosity -> Bool
Eq)

-- |Generation mode.
data GenMode
  = Fresh                                      -- ^ Standard gen (new progression)
  | FromProg Prog.Progression !Int !Int        -- ^ Regenerate range in existing triad layer
  | FromProgPC PC.ProgressionContext !Int !Int -- ^ Regenerate range in a strata-aware context (preserves all three layers + provenance)
  | GridMode                                    -- ^ Static repetition of cue chord
  | StrataMode Sc.StrataLabel                  -- ^ 'Harmonic.Framework.Builder.genP' (strata-first, produces ProgressionContext)

-- |Configuration for the modifier-based generation API.
--
-- Built via modifier chains:
--
-- @
-- s <- seek "*" $ cue start $ tonal ctx $ len 4 $ entropy 0.3 $ gen
-- @
data GenConfig = GenConfig
  { GenConfig -> IO CadenceState
_gcCue         :: IO H.CadenceState  -- ^ Starting state (default: random)
  , GenConfig -> Int
_gcLen         :: Int                 -- ^ Number of chords (default: 4)
  , GenConfig -> String
_gcSeek        :: String              -- ^ Composer blend string (default: "*")
  , GenConfig -> Double
_gcEntropy     :: Double              -- ^ Entropy in [0,1]; gamma shape = 1 + e*9 (default: 0.2)
  , GenConfig -> HarmonicContext
_gcTonal       :: HarmonicContext     -- ^ R constraints (default: hContext)
  , GenConfig -> Verbosity
_gcVerbosity   :: Verbosity           -- ^ Output level (default: Silent)
  , GenConfig -> GenMode
_gcMode        :: GenMode             -- ^ Generation mode (default: Fresh)
  , GenConfig -> Maybe Int
_gcLenOverride :: Maybe Int           -- ^ Set by relStrata \/ absStrata; shadows _gcLen unless len called later
  , GenConfig -> Maybe [Int]
_gcRelStrata   :: Maybe [Int]         -- ^ Per-bar position (1..3) in active tristrata
  , GenConfig -> Maybe [StrataLabel]
_gcAbsStrata   :: Maybe [Sc.StrataLabel] -- ^ Per-bar absolute strata label
  , GenConfig -> Double
_gcBoostSame   :: Double              -- ^ same-strata continuity multiplier (default 0.90)
  , GenConfig -> Double
_gcBoostFlip   :: Double              -- ^ flip-flop bias multiplier        (default 0.80)
  , GenConfig -> Double
_gcBoostTri    :: Double              -- ^ same-tristrata bias multiplier   (default 0.70)
  , GenConfig -> Bool
_gcQuad        :: Bool                -- ^ gen4 family: fuse a 4th R-valid tone into every bar (default False)
  , GenConfig -> Int
_gcMaxAttempts   :: Int               -- ^ Maximum generation attempts in rank-and-select (default 1: single-pass behaviour)
  , GenConfig -> Int
_gcViableTarget  :: Int               -- ^ Stop early once this many viable attempts have been collected (default 1)
  , GenConfig -> Double
_gcViabilityFloor :: Double           -- ^ Minimum 'Harmonic.Evaluation.Scoring.Progression.totalScore' for an attempt to count as viable (default 0.5). Setting 0 recovers structural-only viability.
  }

-------------------------------------------------------------------------------
-- Diagnostics Types
-------------------------------------------------------------------------------

-- |Transform trace captures intermediate values in fromCadenceState → toTriad pipeline.
-- Used for maximum verbosity debugging (gen''). Includes raw DB data plus all transformation stages.
data TransformTrace = TransformTrace
  { TransformTrace -> String
ttRawDbIntervals    :: String    -- ^ Raw zero-form intervals from DB: "[P 0,P 4,P 7]"
  , TransformTrace -> String
ttRawDbMovement     :: String    -- ^ Raw movement from DB: "desc 3"
  , TransformTrace -> String
ttRawDbFunctionality:: String    -- ^ Raw stored functionality from DB: "maj"
  , TransformTrace -> Int
ttRootPC            :: Int       -- ^ Root pitch class (0-11)
  , TransformTrace -> String
ttRootNoteName      :: String    -- ^ Root note name before transform
  , TransformTrace -> [Int]
ttTones             :: [Int]     -- ^ Raw cadence intervals (as Ints) before transposition
  , TransformTrace -> [Int]
ttTransposedPitches :: [Int]     -- ^ Pitches after adding rootPC to tones (STEP 2)
  , TransformTrace -> [Int]
ttNormalizedPs      :: [Int]     -- ^ Result of normalizeWithFund (STEP 3)
  , TransformTrace -> [Int]
ttZeroForm          :: [Int]     -- ^ Result of zeroFormPC (STEP 4)
  , TransformTrace -> String
ttDetectedRoot      :: String    -- ^ Root from detectInversion
  , TransformTrace -> String
ttFunctionality     :: String    -- ^ Result of nameFuncTriad
  , TransformTrace -> String
ttFinalChord        :: String    -- ^ Final rendered chord (root + functionality)
  , TransformTrace -> String
ttStoredFunc        :: String    -- ^ Original functionality stored in cadence
  } deriving (Int -> TransformTrace -> ShowS
[TransformTrace] -> ShowS
TransformTrace -> String
(Int -> TransformTrace -> ShowS)
-> (TransformTrace -> String)
-> ([TransformTrace] -> ShowS)
-> Show TransformTrace
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TransformTrace -> ShowS
showsPrec :: Int -> TransformTrace -> ShowS
$cshow :: TransformTrace -> String
show :: TransformTrace -> String
$cshowList :: [TransformTrace] -> ShowS
showList :: [TransformTrace] -> ShowS
Show, TransformTrace -> TransformTrace -> Bool
(TransformTrace -> TransformTrace -> Bool)
-> (TransformTrace -> TransformTrace -> Bool) -> Eq TransformTrace
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TransformTrace -> TransformTrace -> Bool
== :: TransformTrace -> TransformTrace -> Bool
$c/= :: TransformTrace -> TransformTrace -> Bool
/= :: TransformTrace -> TransformTrace -> Bool
Eq)

-- |Advance trace captures intermediate values in root advancement.
-- Used for maximum verbosity debugging (gen'').
data AdvanceTrace = AdvanceTrace
  { AdvanceTrace -> String
atCurrentRoot       :: String    -- ^ Starting root note name
  , AdvanceTrace -> Int
atCurrentRootPC     :: Int       -- ^ Starting root pitch class
  , AdvanceTrace -> String
atMovement          :: String    -- ^ Movement string (e.g., "asc 3")
  , AdvanceTrace -> Int
atMovementInterval  :: Int       -- ^ Interval as semitones (signed)
  , AdvanceTrace -> Int
atNewRootPC         :: Int       -- ^ Computed new root PC (mod 12)
  , AdvanceTrace -> String
atEnharmFunc        :: String    -- ^ "flat" or "sharp"
  , AdvanceTrace -> String
atNewRoot           :: String    -- ^ Final root note name
  } deriving (Int -> AdvanceTrace -> ShowS
[AdvanceTrace] -> ShowS
AdvanceTrace -> String
(Int -> AdvanceTrace -> ShowS)
-> (AdvanceTrace -> String)
-> ([AdvanceTrace] -> ShowS)
-> Show AdvanceTrace
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AdvanceTrace -> ShowS
showsPrec :: Int -> AdvanceTrace -> ShowS
$cshow :: AdvanceTrace -> String
show :: AdvanceTrace -> String
$cshowList :: [AdvanceTrace] -> ShowS
showList :: [AdvanceTrace] -> ShowS
Show, AdvanceTrace -> AdvanceTrace -> Bool
(AdvanceTrace -> AdvanceTrace -> Bool)
-> (AdvanceTrace -> AdvanceTrace -> Bool) -> Eq AdvanceTrace
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AdvanceTrace -> AdvanceTrace -> Bool
== :: AdvanceTrace -> AdvanceTrace -> Bool
$c/= :: AdvanceTrace -> AdvanceTrace -> Bool
/= :: AdvanceTrace -> AdvanceTrace -> Bool
Eq)

-- |Diagnostic information for a single generation step
data StepDiagnostic = StepDiagnostic
  { StepDiagnostic -> Int
sdStepNumber      :: Int              -- ^ Step number (1-indexed)
  -- PRIOR STATE (before selection)
  , StepDiagnostic -> String
sdPriorCadence    :: String           -- ^ Prior cadence show representation
  , StepDiagnostic -> String
sdPriorRoot       :: String           -- ^ Prior root note
  , StepDiagnostic -> Int
sdPriorRootPC     :: Int              -- ^ Prior root PC (0-11)
  -- SELECTED FROM DB
  , StepDiagnostic -> String
sdSelectedDbIntervals :: String       -- ^ Selected cadence intervals from DB (zero-form)
  , StepDiagnostic -> String
sdSelectedDbMovement  :: String       -- ^ Selected movement from DB
  , StepDiagnostic -> String
sdSelectedDbFunctionality :: String   -- ^ Selected functionality from DB
  -- CANDIDATE POOL INFO
  , StepDiagnostic -> Int
sdGraphCount      :: Int              -- ^ Number of graph candidates found
  , StepDiagnostic -> [(String, Double)]
sdGraphTop6       :: [(String, Double)] -- ^ Top 6 graph candidates with confidence
  , StepDiagnostic -> Int
sdFallbackCount   :: Int              -- ^ Number of fallback candidates used
  , StepDiagnostic -> [(String, Double, Double, Double, Double)]
sdFallbackTop6    :: [(String, Double, Double, Double, Double)] -- ^ Top 6 fallback candidates (name, score, chordDiss, motionDiss, gammaDraw)
  , StepDiagnostic -> Int
sdPoolSize        :: Int              -- ^ Total pool size
  , StepDiagnostic -> Double
sdEntropyUsed     :: Double           -- ^ Entropy value used
  , StepDiagnostic -> Int
sdGammaIndex      :: Int              -- ^ Index selected by gamma sampling
  , StepDiagnostic -> String
sdSelectedFrom    :: String           -- ^ "graph" or "fallback"
  -- POSTERIOR STATE (after advance)
  , StepDiagnostic -> String
sdPosteriorRoot   :: String           -- ^ Posterior root note after advancement
  , StepDiagnostic -> Int
sdPosteriorRootPC :: Int              -- ^ Posterior root PC (0-11)
  -- Verbosity 1+ fields (Nothing at verbosity 0)
  , StepDiagnostic -> Maybe String
sdRenderedChord   :: Maybe String     -- ^ Actual chord after fromCadenceState (verbosity 1+)
  -- Verbosity 2 fields (Nothing at verbosity 0 or 1)
  , StepDiagnostic -> Maybe TransformTrace
sdTransformTrace  :: Maybe TransformTrace  -- ^ Full transform trace (verbosity 2)
  , StepDiagnostic -> Maybe AdvanceTrace
sdAdvanceTrace    :: Maybe AdvanceTrace    -- ^ Full advance trace (verbosity 2)
  -- 'Harmonic.Framework.Builder.genP' fields — populated post-step by @runStrataGen@ via 'Strata.hs'.
  -- 'Nothing' for every non-StrataMode run (legacy 'Harmonic.Framework.Builder.gen' paths and
  -- diagnostic callers that don't know about strata).
  , StepDiagnostic -> Maybe Int
sdTristrataIdx    :: Maybe Int               -- ^ 1-based index into 'Sc.validTristrata'
  , StepDiagnostic -> Maybe Tristrata
sdTristrata       :: Maybe Sc.Tristrata      -- ^ Active tristrata for this bar
  , StepDiagnostic -> Maybe StrataLabel
sdStrataLabel     :: Maybe Sc.StrataLabel    -- ^ Selected strata for this bar
  , StepDiagnostic -> Maybe Mode
sdMode            :: Maybe Sc.Mode           -- ^ Pair-union mode (strata_prev ∪ strata_curr). 'Nothing' when the bar's 'sdModeResult' is 'Harmonic.Rules.Types.Scale.ModeInvalid' (override-driven 6-PC overlap that doesn't classify as a 7-PC mode).
  , StepDiagnostic -> Maybe [PitchClass]
sdStrataChroma    :: Maybe [P.PitchClass]    -- ^ 5-PC strata chroma
  , StepDiagnostic -> Maybe [PitchClass]
sdModeChroma      :: Maybe [P.PitchClass]    -- ^ Mode\/overlap chroma — 7 PCs for 'Harmonic.Rules.Types.Scale.ModeOk', 6 PCs for override-driven 'Harmonic.Rules.Types.Scale.ModeInvalid' bars.
  , StepDiagnostic -> Maybe Double
sdSoftBoost       :: Maybe Double            -- ^ Boost product applied this bar
  , StepDiagnostic -> Maybe Int
sdHarmonicRootPC  :: Maybe Int               -- ^ Triad's harmonic root PC (post-detectInversion). 'sdPosteriorRootPC' is the cadence's stored root, which for inversions is the bass — for the strata pivot we want this field instead.
  , StepDiagnostic -> Maybe (PitchClass, ScaleFamily)
sdParentKey       :: Maybe (P.PitchClass, Sc.ScaleFamily) -- ^ Parent key (root, family) of 'sdMode'
  , StepDiagnostic -> Maybe ModeResult
sdModeResult      :: Maybe Sc.ModeResult     -- ^ Raw mode classification result; 'Harmonic.Rules.Types.Scale.ModeInvalid' surfaces overlap PCs to the renderer
  , StepDiagnostic -> Maybe EnharmonicSpelling
sdBarSpelling     :: Maybe H.EnharmonicSpelling -- ^ Single enharmonic spelling for the bar, derived via 'H.inferSpelling' on the mode chroma (triad-root-first). Reused across chord name, strata chroma, mode label, mode chroma, and parent-key tag so the block renders in one coherent accidental system.
  , StepDiagnostic -> Maybe FusionDiag
sdFusion          :: Maybe FusionDiag        -- ^ gen4 only: how the 4th tone was fused into this bar ('Nothing' for plain gen\/genP steps and palette-exhausted fallback bars)
  } deriving (Int -> StepDiagnostic -> ShowS
[StepDiagnostic] -> ShowS
StepDiagnostic -> String
(Int -> StepDiagnostic -> ShowS)
-> (StepDiagnostic -> String)
-> ([StepDiagnostic] -> ShowS)
-> Show StepDiagnostic
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> StepDiagnostic -> ShowS
showsPrec :: Int -> StepDiagnostic -> ShowS
$cshow :: StepDiagnostic -> String
show :: StepDiagnostic -> String
$cshowList :: [StepDiagnostic] -> ShowS
showList :: [StepDiagnostic] -> ShowS
Show, StepDiagnostic -> StepDiagnostic -> Bool
(StepDiagnostic -> StepDiagnostic -> Bool)
-> (StepDiagnostic -> StepDiagnostic -> Bool) -> Eq StepDiagnostic
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: StepDiagnostic -> StepDiagnostic -> Bool
== :: StepDiagnostic -> StepDiagnostic -> Bool
$c/= :: StepDiagnostic -> StepDiagnostic -> Bool
/= :: StepDiagnostic -> StepDiagnostic -> Bool
Eq)

-- |Diagnostic record for one gen4 fusion (the added-tone draw that turns
-- the selected triad into a 4-note chord).
data FusionDiag = FusionDiag
  { FusionDiag -> Int
fdAddedPC   :: Int     -- ^ Absolute pitch class of the added tone
  , FusionDiag -> String
fdFusedName :: String  -- ^ Functionality of the fused 4-note chord
  , FusionDiag -> Int
fdGammaIdx  :: Int     -- ^ Gamma-selected index into the consonant-first ranking (0 = most consonant)
  , FusionDiag -> Int
fdPoolK     :: Int     -- ^ Number of fusion candidates (palette \\ triad, post-drift)
  } deriving (Int -> FusionDiag -> ShowS
[FusionDiag] -> ShowS
FusionDiag -> String
(Int -> FusionDiag -> ShowS)
-> (FusionDiag -> String)
-> ([FusionDiag] -> ShowS)
-> Show FusionDiag
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FusionDiag -> ShowS
showsPrec :: Int -> FusionDiag -> ShowS
$cshow :: FusionDiag -> String
show :: FusionDiag -> String
$cshowList :: [FusionDiag] -> ShowS
showList :: [FusionDiag] -> ShowS
Show, FusionDiag -> FusionDiag -> Bool
(FusionDiag -> FusionDiag -> Bool)
-> (FusionDiag -> FusionDiag -> Bool) -> Eq FusionDiag
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FusionDiag -> FusionDiag -> Bool
== :: FusionDiag -> FusionDiag -> Bool
$c/= :: FusionDiag -> FusionDiag -> Bool
/= :: FusionDiag -> FusionDiag -> Bool
Eq)

-- |Complete diagnostics for a generation run
data GenerationDiagnostics = GenerationDiagnostics
  { GenerationDiagnostics -> String
gdStartCadence    :: String           -- ^ Starting cadence
  , GenerationDiagnostics -> String
gdStartRoot       :: String           -- ^ Starting root note
  , GenerationDiagnostics -> Int
gdRequestedLen    :: Int              -- ^ Requested progression length
  , GenerationDiagnostics -> Int
gdActualLen       :: Int              -- ^ Actual progression length
  , GenerationDiagnostics -> Double
gdEntropy         :: Double           -- ^ Entropy parameter used
  , GenerationDiagnostics -> [StepDiagnostic]
gdSteps           :: [StepDiagnostic] -- ^ Per-step diagnostics
  , GenerationDiagnostics -> Progression
gdProgression     :: Prog.Progression -- ^ The generated progression
  } deriving (Int -> GenerationDiagnostics -> ShowS
[GenerationDiagnostics] -> ShowS
GenerationDiagnostics -> String
(Int -> GenerationDiagnostics -> ShowS)
-> (GenerationDiagnostics -> String)
-> ([GenerationDiagnostics] -> ShowS)
-> Show GenerationDiagnostics
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> GenerationDiagnostics -> ShowS
showsPrec :: Int -> GenerationDiagnostics -> ShowS
$cshow :: GenerationDiagnostics -> String
show :: GenerationDiagnostics -> String
$cshowList :: [GenerationDiagnostics] -> ShowS
showList :: [GenerationDiagnostics] -> ShowS
Show)

-- |Per-attempt diagnostic record for the multi-attempt rank-and-select
-- (@generateBest@) loop. Captured once per attempt and surfaced at
-- 'Verbose' via 'Harmonic.Framework.Builder.Diagnostics.printAttemptScoreboard'. At 'Silent' \/ 'Standard' the
-- list is discarded after the winner is picked.
data AttemptDiagnostic = AttemptDiagnostic
  { AttemptDiagnostic -> Int
adIndex  :: !Int                   -- ^ 1-based generation order
  , AttemptDiagnostic -> ProgressionScore
adScore  :: !PS.ProgressionScore   -- ^ Per-axis breakdown (rm\/vl\/cf\/mv)
  , AttemptDiagnostic -> Double
adTotal  :: !Double                -- ^ Weighted total ('PS.totalScore')
  , AttemptDiagnostic -> Bool
adViable :: !Bool                  -- ^ Passed viability check ('psModeValidity >= 1 && tot >= floor')
  , AttemptDiagnostic -> Bool
adPicked :: !Bool                  -- ^ True iff the winner ('maximumByKey adTotal')
  , AttemptDiagnostic -> [String]
adChords :: ![String]              -- ^ Chord-name sequence for the diff column in the scoreboard
  } deriving (Int -> AttemptDiagnostic -> ShowS
[AttemptDiagnostic] -> ShowS
AttemptDiagnostic -> String
(Int -> AttemptDiagnostic -> ShowS)
-> (AttemptDiagnostic -> String)
-> ([AttemptDiagnostic] -> ShowS)
-> Show AttemptDiagnostic
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AttemptDiagnostic -> ShowS
showsPrec :: Int -> AttemptDiagnostic -> ShowS
$cshow :: AttemptDiagnostic -> String
show :: AttemptDiagnostic -> String
$cshowList :: [AttemptDiagnostic] -> ShowS
showList :: [AttemptDiagnostic] -> ShowS
Show, AttemptDiagnostic -> AttemptDiagnostic -> Bool
(AttemptDiagnostic -> AttemptDiagnostic -> Bool)
-> (AttemptDiagnostic -> AttemptDiagnostic -> Bool)
-> Eq AttemptDiagnostic
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AttemptDiagnostic -> AttemptDiagnostic -> Bool
== :: AttemptDiagnostic -> AttemptDiagnostic -> Bool
$c/= :: AttemptDiagnostic -> AttemptDiagnostic -> Bool
/= :: AttemptDiagnostic -> AttemptDiagnostic -> Bool
Eq)