"""Small reporting contracts; they do not identify or validate an assay model."""
from dataclasses import dataclass
from fractions import Fraction as F


@dataclass(frozen=True)
class Claim:
    target:str
    unit:str
    condition:str
    threshold:F
    def __post_init__(self):
        if not self.target or not self.unit or not self.condition:raise ValueError('Name target, unit and task conditions.')


@dataclass(frozen=True)
class DeterministicEnclosure:
    lower:F|None
    upper:F|None
    feasible_witness:bool=False
    proved_empty:bool=False
    evaluable:bool=True
    def decide(self,claim):
        if not self.evaluable:return 'unevaluable'
        if self.proved_empty or (self.lower is not None and self.upper is not None and self.lower>self.upper):return 'incompatible'
        if not self.feasible_witness:return 'unresolved: feasibility not established'
        if self.lower is not None and self.lower>=claim.threshold:return 'supported'
        if self.upper is not None and self.upper<claim.threshold:return 'excluded'
        return 'unresolved'


@dataclass(frozen=True)
class IssuanceGuarantee:
    error_upper:F
    allowance:F
    experiment:str
    def __post_init__(self):
        if not 0<=self.error_upper<=1 or not 0<=self.allowance<=1 or not self.experiment:raise ValueError('Explicit experiment and probability bounds required.')
    def apply(self,gate_passed,negative,complete=True):
        return dict(issued=bool(complete and gate_passed and negative and self.error_upper<=self.allowance),
            status='unevaluable' if not complete else 'rule evaluated',
            guarantee_kind='joint false issuance over the complete repeated experiment',
            error_upper=self.error_upper,experiment=self.experiment,
            interpretation='Not a posterior probability and not the same error bound conditional on gate passage.')


@dataclass(frozen=True)
class PredictionRegion:
    cutoff:int
    coverage:F
    required:F
    preparation:str
    endpoint:str='Biological count from two founders at T=log(2)'
    def evaluate(self):
        if type(self.cutoff)!=int or self.cutoff<0 or not 0<=self.coverage<=1 or not 0<=self.required<=1:raise ValueError('Invalid finite prediction region.')
        return dict(certified=self.coverage>=self.required,cutoff=self.cutoff,coverage=self.coverage,
            guarantee_kind='future-count prediction under the named preparation law',preparation=self.preparation,endpoint=self.endpoint)


def specimen_bound(a,K,m):
    a=F(a)
    if not 0<=a<=F(1,2) or type(K)!=int or K<1 or type(m)!=int or m<1:raise ValueError('Balanced unique fractions and positive counts required.')
    H=(1-a)**K;z=F(1) if H==1 else min(F(1),F(m,m+1)/(1-H))
    return z**m*(1-(1-H)*z),z


def path_floor(coverage,mismatch):
    c,d=F(coverage),F(mismatch)
    if not 0<=c<=2 or d<0:raise ValueError('Coverage must be in [0,2]; mismatch nonnegative.')
    t=min(d,c,2-c);x,y=(c+t)/2,(c-t)/2
    return dict(floor=(c*c-t*t)/4,attaining_stages=(x,y,y,x))


def negative_recovery_bound(n,theta,g,calibration_failure):
    theta,g,delta=map(F,(theta,g,calibration_failure))
    if type(n)!=int or n<2 or n%2 or not all(0<=v<=1 for v in (theta,g,delta)):raise ValueError('Even positive allocation and probabilities required.')
    return delta+(1-delta)*(1-theta*g)**n


def founder_cdf(k,shared=False):
    if type(k)!=int:raise ValueError('Integer cutoff required.')
    if k<2:return F(0)
    return 1-F(k+1,2**(k+1)) if shared else 1-F(k+5,4*2**k)


def thinned_cdf_two(detection,shared=False):
    z=F(detection)
    if not 0<=z<=1:raise ValueError('Detection probability outside [0,1].')
    slow=[1-z,z,F(0)];fast=[(1-z)/(1+z),2*z/(1+z)**2,2*z*z/(1+z)**3]
    def conv(a):return sum(a[i]*a[j] for i in range(3) for j in range(3-i))
    return (conv(slow)+conv(fast))/2 if shared else conv([(a+b)/2 for a,b in zip(slow,fast)])
