# Bounded roof surfaces from a cross profile intersected with optional end slopes.
from dataclasses import dataclass


@dataclass(frozen=True)
class RoofSurface:
    """Evaluate a local roof height and uphill direction without writing geometry."""
    levels: tuple[int, ...]
    length: int
    end_hips: tuple[int, int] = (0, 0)

    def __post_init__(self):
        if len(self.end_hips) != 2 or any(type(v) is not int or not 0 <= v <= 3 for v in self.end_hips):
            raise ValueError('Roof end hips require two integer depths in 0..3')
        if max(self.levels) - max(self.end_hips) < 4 or sum(self.end_hips) >= self.length - 1:
            raise ValueError('Roof hips must retain four-cell gables and a nonzero ridge')

    @property
    def span(self):
        return len(self.levels) - 2

    @property
    def peak(self):
        return max(self.levels)

    @property
    def ridge(self):
        a, b = self.end_hips
        return (a if a else -1, self.length - 1 - b if b else self.length)

    def height(self, x, z):
        value = self.levels[x + 1]
        a, b = self.end_hips
        if a:
            value = min(value, self.peak - a + z)
        if b:
            value = min(value, self.peak - b + self.length - 1 - z)
        return value

    def facing(self, x, z):
        a, b = self.end_hips
        across = self.levels[x + 1]
        if a and self.height(x, z) < across and z < a:
            return 'south'
        if b and self.height(x, z) < across and z > self.length - 1 - b:
            return 'north'
        return 'east' if x < self.span // 2 else 'west'

    def previous(self, x, z):
        neighbors = [(a, b) for a, b in ((x - 1, z), (x + 1, z), (x, z - 1), (x, z + 1)) if -1 <= a <= self.span and -1 <= b <= self.length]
        return min(self.height(a, b) for a, b in neighbors)
