"""Based on Gerber file spec.

https://www.ucamco.com/files/downloads/file_en/456/gerber-layer-format-specification-revision-2022-02_en.pdf.

See Also:
- https://github.com/opiopan/pcb-tools-extension
- https://github.com/jamesbowman/cuflow/blob/master/gerber.py
"""

from pathlib import Path
from typing import Literal

from pydantic import BaseModel, Field

from gdsfactory import Component
from gdsfactory.typings import Size


class GerberLayer(BaseModel):
    name: str
    function: list[str]
    polarity: Literal["Positive", "Negative"]


class GerberOptions(BaseModel):
    header: list[str] | None = None
    mode: Literal["mm", "in"] = "mm"
    resolution: float = 1e-6
    int_size: int = 4


# For generating a gerber job json file
class BoardOptions(BaseModel):
    size: Size | None = None
    n_layers: int = 2


resolutions = {1e-3: 3, 1e-4: 4, 1e-5: 5, 1e-6: 6}


def number(n: float) -> str:
    """Formats a floating-point number by scaling it to an integer (multiplied by 10,000).

    Rounding to the nearest integer, and zero-padding it to 7 characters.

    Args:
        n (float): The input floating-point number.

    Returns:
        str: The formatted string.
    """
    scaled_value = round(n * 10000)
    return f"{scaled_value:07d}"


def points(pp: list[tuple[float, float]]) -> str:
    if not pp:
        return ""
    # Use a list to collect the formatted strings for better performance
    parts = []
    # First point uses D02
    x0, y0 = pp[0]
    parts.append(f"X{number(x0)}Y{number(y0)}D02*\n")
    # Rest use D01 (if any)
    if len(pp) > 1:
        for x, y in pp[1:]:
            parts.append(f"X{number(x)}Y{number(y)}D01*\n")
    return "".join(parts)


def rect(x0: float, y0: float, x1: float, y1: float) -> str:
    return "D10*\n" + points([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)])


def linestring(pp: list[tuple[float, float]]) -> str:
    return "D10*\n" + points(pp)


def polygon(pp: list[tuple[float, float]]) -> str:
    return "G36*\n" + points(pp) + "G37*\n\n"  # fixed to have only one newline at end


def to_gerber(
    component: Component,
    dirpath: Path,
    layermap_to_gerber_layer: dict[tuple[int, int], GerberLayer],
    options: GerberOptions = Field(default_factory=GerberOptions),
) -> None:
    """Writes each layer to a different Gerber file.

    Args:
        component: to export.
        dirpath: directory path.
        layermap_to_gerber_layer: map of GDS layer to GerberLayer.
        options: to save.
            header: List[str] | None = None
            mode: Literal["mm", "in"] = "mm"
            resolution: float = 1e-6
            int_size: int = 4
    """
    # Split references into polygons and circles (components will need to be recursively iterated through)
    # for ref in component.references:
    #     if ref.parent_cell.name.startswith("circle"):
    #         radius = ref.parent_cell.settings["radius"]
    #         center = ref.center
    # Each layer and a list of the polygons (as lists of points) on that layer
    layer_to_polygons = component.get_polygons_points()

    for layer_tup, layer in layermap_to_gerber_layer.items():
        filename = (dirpath / layer.name.replace(" ", "_")).with_suffix(".gbr")

        with open(filename, "w+") as f:
            header = options.header or [
                "Gerber file generated by gdsfactory",
                f"Component: {component.name}",
            ]

            # Write file spec info
            f.write("%TF.FileFunction," + ",".join(layer.function) + "*%\n")
            f.write(f"%TF.FilePolarity,{layer.polarity}*%\n")

            digits = resolutions[options.resolution]
            f.write(f"%FSLA{options.int_size}{digits}Y{options.int_size}{digits}X*%\n")

            # Write header comments
            f.writelines([f"G04 {line}*\n" for line in header])

            # Setup units/mode
            units = options.mode.upper()
            f.write(f"%MO{units}*%\n")
            f.write("%LPD*%")

            f.write("G01*\n")

            # Aperture definition
            f.write("%ADD10C,0.050000*%\n")

            # Only supports polygons for now
            if layer_tup in layer_to_polygons:
                f.writelines(polygon(poly) for poly in layer_to_polygons[layer_tup])  # type: ignore[arg-type]

            # File end
            f.write("M02*\n")
