# Frozen field sampling, strata and water under scoped edit interfaces.
import math
from dataclasses import dataclass
from hearth.space import Box, Frame
from hearth.blocks import state_of
from hearth.kernel import Capability, Contract, Grant, Port, Plan, Rule
from .fields import Field


@dataclass(frozen=True)
class Terrain:
    area: Box
    field: Field
    water_level: int | None = None
    edit_limit: int = 2500

    def capability(self):
        return Capability('terrain.field', ('environment', 'ground'), offers=('construction', 'planting'), guarantees=('frozen field inputs', 'actual 3D substrate'))

    def negotiate(self, ctx, parameters):
        ports = tuple(Port(k, k, Frame(self.area.lo), self.area, capacity=100000, grant=k) for k in ('construction', 'planting'))
        grants = (Grant('construction', self.area, ('excavate', 'fill', 'surface'), ('siteworks', 'path', 'pool'), self.edit_limit), Grant('planting', self.area, ('plant',), ('plant',), 0))
        return Contract(self.area, ports, grants, decisions={'expression': self.field.expression(), 'water': self.water_level, 'edit_limit': self.edit_limit})

    def realize(self, ctx, contract):
        plan = Plan()
        x0, bottom, z0 = self.area.lo
        x1, top, z1 = self.area.hi
        for x in range(x0, x1 + 1):
            for z in range(z0, z1 + 1):
                height = math.floor(self.field(x, z))
                if not bottom <= height < top:
                    raise ValueError(f'Terrain field exceeds negotiated vertical domain at {(x,z)}')
                for y in range(bottom, height + 1):
                    material = 'stone' if y < height - 2 else ('dirt' if y < height else 'grass_block')
                    if self.water_level is not None and height < self.water_level:
                        material = 'gravel' if y == height else material
                    plan.block((x, y, z), state_of(material), 'strata')
                if self.water_level is not None:
                    for y in range(height + 1, self.water_level + 1):
                        plan.block((x, y, z), state_of('water', level='0'), 'water-volume')
        return plan
