"""
Material quantity estimation for concrete projects.

**NEW** — these calculations go beyond what is currently available on
https://www.slabcalc.co and cover rebar, welded wire mesh, and formwork lumber.
"""

from __future__ import annotations

import math
from typing import TypedDict

__all__ = [
    "rebar_estimate",
    "wire_mesh_estimate",
    "form_lumber_estimate",
    "RebarResult",
    "WireMeshResult",
    "FormLumberResult",
]


class RebarResult(TypedDict):
    total_length_ft: float
    pieces_20ft: int
    weight_lbs: float
    spacing_in: int


# Pounds per linear foot by rebar size (US standard).
REBAR_WEIGHT: dict[int, float] = {
    3: 0.376,   # #3  (3/8")
    4: 0.668,   # #4  (1/2")
    5: 1.043,   # #5  (5/8")
    6: 1.502,   # #6  (3/4")
    7: 2.044,   # #7  (7/8")
    8: 2.670,   # #8  (1")
}


def rebar_estimate(
    length_ft: float,
    width_ft: float,
    spacing_in: int = 12,
    bar_size: int = 4,
    both_directions: bool = True,
) -> RebarResult:
    """Estimate rebar needed for a rectangular slab.

    Args:
        length_ft: Slab length in feet.
        width_ft: Slab width in feet.
        spacing_in: On-center spacing in inches (commonly 12, 16, or 18).
        bar_size: US rebar size number (3-8).
        both_directions: If True, calculate a grid (both ways); otherwise one direction only.

    Returns:
        Dict with total length, number of 20 ft pieces, weight, and spacing used.
    """
    weight_per_ft = REBAR_WEIGHT.get(bar_size)
    if weight_per_ft is None:
        raise ValueError(f"Unknown bar size #{bar_size}. Use 3, 4, 5, 6, 7, or 8.")

    spacing_ft = spacing_in / 12.0

    # Bars running along the length (spaced across the width)
    bars_across_width = math.floor(width_ft / spacing_ft) + 1
    length_dir1 = bars_across_width * length_ft

    total = length_dir1
    if both_directions:
        bars_across_length = math.floor(length_ft / spacing_ft) + 1
        length_dir2 = bars_across_length * width_ft
        total += length_dir2

    pieces = math.ceil(total / 20.0)
    weight = total * weight_per_ft

    return RebarResult(
        total_length_ft=round(total, 1),
        pieces_20ft=pieces,
        weight_lbs=round(weight, 1),
        spacing_in=spacing_in,
    )


class WireMeshResult(TypedDict):
    area_sqft: float
    sheets_5x10: int


def wire_mesh_estimate(
    length_ft: float,
    width_ft: float,
    overlap_in: int = 6,
) -> WireMeshResult:
    """Estimate welded wire mesh (WWM) sheets for a rectangular slab.

    Standard sheet size is 5 ft x 10 ft with *overlap_in* inches of overlap
    on each seam.

    Args:
        length_ft: Slab length.
        width_ft: Slab width.
        overlap_in: Overlap per seam in inches (industry standard 6").

    Returns:
        Dict with total area and number of 5x10 sheets needed.
    """
    area = length_ft * width_ft

    # Effective coverage per sheet accounting for overlap
    sheet_w = 5.0 - overlap_in / 12.0
    sheet_l = 10.0 - overlap_in / 12.0
    effective = sheet_w * sheet_l

    sheets = math.ceil(area / effective)
    return WireMeshResult(area_sqft=round(area, 1), sheets_5x10=sheets)


class FormLumberResult(TypedDict):
    perimeter_ft: float
    boards_8ft: int
    stakes: int


def form_lumber_estimate(
    length_ft: float,
    width_ft: float,
    slab_thickness_in: float = 4.0,
    stake_spacing_ft: float = 3.0,
) -> FormLumberResult:
    """Estimate form lumber for a rectangular pour.

    Assumes standard 2x lumber forms (2x4, 2x6, etc.) around the slab
    perimeter, with wooden stakes at regular intervals.

    Args:
        length_ft: Slab length.
        width_ft: Slab width.
        slab_thickness_in: Thickness (used to recommend board size, not volume).
        stake_spacing_ft: Distance between stakes along the perimeter.

    Returns:
        Dict with perimeter, number of 8 ft boards, and number of stakes.
    """
    perimeter = 2 * (length_ft + width_ft)
    boards = math.ceil(perimeter / 8.0)
    stakes = math.ceil(perimeter / stake_spacing_ft) + 4  # +4 for corners
    return FormLumberResult(
        perimeter_ft=round(perimeter, 1),
        boards_8ft=boards,
        stakes=stakes,
    )
