#!/usr/bin/env python3

from __future__ import annotations

import argparse
import json
from pathlib import Path

import cadquery as cq


def clamp(value, low, high):
    return max(low, min(high, value))


def build_nameplate(
    text: str,
    font_path: str,
):
    text = str(text).strip()

    if not text:
        raise ValueError("Nameplate text cannot be empty.")

    if len(text) > 18:
        raise ValueError(
            "Nameplate text currently supports a maximum of 18 characters."
        )

    # Automatically grow the plaque for longer names.
    width = clamp(
        82 + (len(text) * 9.5),
        125,
        235,
    )

    height = 58.0
    plaque_thickness = 4.0
    text_height = 1.6

    base_width = width + 20.0
    base_depth = 32.0
    base_height = 10.0

    fit_clearance = 0.55
    slot_width = plaque_thickness + fit_clearance
    slot_depth = 6.0

    # Keep longer names readable.
    font_size = clamp(
        (width - 22) / max(len(text) * 0.62, 1),
        18,
        29,
    )

    # ----------------------------------------------------------
    # PLAQUE
    # ----------------------------------------------------------

    plaque = (
        cq.Workplane("XY")
        .box(
            width,
            height,
            plaque_thickness,
            centered=(True, True, False),
        )
        .edges("|Z")
        .fillet(4.0)
    )

    # Decorative inner border.
    border_outer = (
        cq.Workplane("XY")
        .workplane(offset=plaque_thickness)
        .rect(width - 10, height - 10)
        .rect(width - 14, height - 14)
        .extrude(0.8)
    )

    # ----------------------------------------------------------
    # TEXT
    # ----------------------------------------------------------

    text_kwargs = {
        "txt": text,
        "fontsize": font_size,
        "distance": text_height,
        "combine": True,
        "clean": True,
        "halign": "center",
        "valign": "center",
        "fontPath": font_path,
    }

    lettering = (
        cq.Workplane("XY")
        .workplane(offset=plaque_thickness)
        .text(**text_kwargs)
    )

    # Single-colour printable version.
    combined_plaque = plaque.union(border_outer).union(lettering)

    # ----------------------------------------------------------
    # DESK BASE
    # ----------------------------------------------------------

    stand = (
        cq.Workplane("XY")
        .box(
            base_width,
            base_depth,
            base_height,
            centered=(True, True, False),
        )
        .edges("|Z")
        .fillet(3.0)
    )

    # Long open slot accepts the plaque.
    slot = (
        cq.Workplane("XY")
        .box(
            width + 1.2,
            slot_width,
            slot_depth,
            centered=(True, True, False),
        )
        .translate(
            (
                0,
                0,
                base_height - slot_depth,
            )
        )
    )

    stand = stand.cut(slot)

    # ----------------------------------------------------------
    # METADATA
    # ----------------------------------------------------------

    metadata = {
        "product": "SHiRE 3D Parametric Desk Nameplate",
        "text": text,
        "dimensions_mm": {
            "plaque_width": round(width, 2),
            "plaque_height": height,
            "plaque_thickness": plaque_thickness,
            "raised_text": text_height,
            "base_width": round(base_width, 2),
            "base_depth": base_depth,
            "base_height": base_height,
            "slot_width": round(slot_width, 2),
            "slot_depth": slot_depth,
        },
        "font_size_mm": round(font_size, 2),
        "fit_clearance_mm": fit_clearance,
        "design_intent": {
            "support_free_parts": True,
            "multi_colour_capable": True,
            "single_colour_capable": True,
            "parametric_text": True,
            "customer_personalisation": True,
        },
    }

    return {
        "plaque": plaque,
        "border": border_outer,
        "text": lettering,
        "combined": combined_plaque,
        "stand": stand,
        "metadata": metadata,
    }


def main():
    parser = argparse.ArgumentParser()

    parser.add_argument(
        "--text",
        required=True,
    )

    parser.add_argument(
        "--font",
        required=True,
    )

    parser.add_argument(
        "--output",
        required=True,
    )

    args = parser.parse_args()

    output = Path(args.output)
    output.mkdir(parents=True, exist_ok=True)

    model = build_nameplate(
        args.text,
        args.font,
    )

    cq.exporters.export(
        model["plaque"],
        str(output / "nameplate_plaque.stl"),
    )

    cq.exporters.export(
        model["text"],
        str(output / "nameplate_text.stl"),
    )

    cq.exporters.export(
        model["combined"],
        str(output / "nameplate_single_colour.stl"),
    )

    cq.exporters.export(
        model["stand"],
        str(output / "nameplate_stand.stl"),
    )

    cq.exporters.export(
        model["combined"],
        str(output / "nameplate_plaque.step"),
    )

    cq.exporters.export(
        model["stand"],
        str(output / "nameplate_stand.step"),
    )

    (output / "design.json").write_text(
        json.dumps(
            model["metadata"],
            indent=2,
        ),
        encoding="utf-8",
    )

    print(
        json.dumps(
            model["metadata"],
            indent=2,
        )
    )


if __name__ == "__main__":
    main()
