"""
KiCad PCB Export - Export PCBLayout to KiCad PCB format.

This module provides:
- KiCadPCBExporter: Export PCBLayout to .kicad_pcb files
- update_pcb_placements: Update component positions in existing PCB files
"""

import re
import uuid
from pathlib import Path

from .layout import PCBLayout


class KiCadPCBExporter:
    """
    Export PCBLayout to KiCad PCB format (.kicad_pcb).

    Can either:
    1. Generate a new PCB file with placements and traces
    2. Update an existing PCB file with new placements/traces

    Usage:
        exporter = KiCadPCBExporter(layout)
        exporter.write("output.kicad_pcb")

        # Or update existing file
        exporter.update_placements("existing.kicad_pcb", "output.kicad_pcb")
    """

    def __init__(self, layout: PCBLayout):
        self.layout = layout
        self.nets: dict[str, int] = {}  # net name -> net number
        self._next_net = 1

    def _get_net_number(self, net_name: str | None) -> int:
        """Get or assign a net number."""
        if net_name is None:
            return 0
        if net_name not in self.nets:
            self.nets[net_name] = self._next_net
            self._next_net += 1
        return self.nets[net_name]

    def _uuid(self) -> str:
        """Generate a UUID for KiCad elements."""
        return str(uuid.uuid4())

    def _format_coord(self, val: float) -> str:
        """Format coordinate value."""
        return f"{val:.4f}"

    def _generate_header(self, title: str = "PCB Layout") -> str:
        """Generate PCB file header."""
        return f'''(kicad_pcb
	(version 20241229)
	(generator "kicad_pcb_blocks.py")
	(generator_version "1.0")
	(general
		(thickness 1.6)
		(legacy_teardrops no)
	)
	(paper "A4")
	(title_block
		(title "{title}")
		(date "2025-01")
		(rev "A")
		(comment 1 "Generated by kicad_pcb_blocks.py")
		(comment 2 "Virtual component / block-based layout")
	)
	(layers
		(0 "F.Cu" signal)
		(31 "B.Cu" signal)
		(32 "B.Adhes" user "B.Adhesive")
		(33 "F.Adhes" user "F.Adhesive")
		(34 "B.Paste" user)
		(35 "F.Paste" user)
		(36 "B.SilkS" user "B.Silkscreen")
		(37 "F.SilkS" user "F.Silkscreen")
		(38 "B.Mask" user)
		(39 "F.Mask" user)
		(40 "Dwgs.User" user "User.Drawings")
		(41 "Cmts.User" user "User.Comments")
		(44 "Edge.Cuts" user)
		(46 "B.CrtYd" user "B.Courtyard")
		(47 "F.CrtYd" user "F.Courtyard")
		(48 "B.Fab" user)
		(49 "F.Fab" user)
	)
	(setup
		(pad_to_mask_clearance 0.05)
		(allow_soldermask_bridges_in_footprints no)
	)
'''

    def _generate_nets(self) -> str:
        """Generate net definitions."""
        lines = ['\t(net 0 "")']

        # Collect all nets from traces
        for block in self.layout.blocks.values():
            for trace in block.traces:
                if trace.net:
                    self._get_net_number(trace.net)

        for trace in self.layout.inter_block_traces:
            if trace.net:
                self._get_net_number(trace.net)

        # Generate net lines
        for net_name, net_num in sorted(self.nets.items(), key=lambda x: x[1]):
            lines.append(f'\t(net {net_num} "{net_name}")')

        return "\n".join(lines)

    def _generate_footprint(
        self,
        ref: str,
        footprint: str,
        x: float,
        y: float,
        rotation: float,
        layer: str,
        value: str = "",
    ) -> str:
        """Generate a footprint placement."""
        fp_uuid = self._uuid()
        ref_uuid = self._uuid()
        val_uuid = self._uuid()
        fp_prop_uuid = self._uuid()

        return f'''	(footprint "{footprint}"
		(layer "{layer}")
		(uuid "{fp_uuid}")
		(at {self._format_coord(x)} {self._format_coord(y)} {rotation})
		(property "Reference" "{ref}"
			(at 0 -2 0)
			(layer "F.SilkS")
			(uuid "{ref_uuid}")
			(effects
				(font
					(size 0.8 0.8)
					(thickness 0.12)
				)
			)
		)
		(property "Value" "{value}"
			(at 0 2 0)
			(layer "F.Fab")
			(uuid "{val_uuid}")
			(effects
				(font
					(size 0.8 0.8)
					(thickness 0.12)
				)
			)
		)
		(property "Footprint" "{footprint}"
			(at 0 0 0)
			(layer "F.Fab")
			(hide yes)
			(uuid "{fp_prop_uuid}")
			(effects
				(font
					(size 1 1)
					(thickness 0.15)
				)
			)
		)
	)'''

    def _generate_segment(
        self, start: tuple, end: tuple, width: float, layer: str, net: int
    ) -> str:
        """Generate a trace segment."""
        seg_uuid = self._uuid()
        return f'''	(segment
		(start {self._format_coord(start[0])} {self._format_coord(start[1])})
		(end {self._format_coord(end[0])} {self._format_coord(end[1])})
		(width {width})
		(layer "{layer}")
		(net {net})
		(uuid "{seg_uuid}")
	)'''

    def _generate_via(
        self, x: float, y: float, size: float, drill: float, layers: tuple, net: int
    ) -> str:
        """Generate a via."""
        via_uuid = self._uuid()
        layer_str = f'"{layers[0]}" "{layers[1]}"'
        return f'''	(via
		(at {self._format_coord(x)} {self._format_coord(y)})
		(size {size})
		(drill {drill})
		(layers {layer_str})
		(net {net})
		(uuid "{via_uuid}")
	)'''

    def generate(self, title: str | None = None) -> str:
        """Generate complete KiCad PCB file content."""
        if title is None:
            title = self.layout.name

        sections = []

        # Header
        sections.append(self._generate_header(title))

        # Nets
        sections.append(self._generate_nets())

        # Footprints
        footprint_lines = []
        for placement in self.layout.export_placements():
            fp = self._generate_footprint(
                ref=placement["ref"],
                footprint=placement["footprint"],
                x=placement["x"],
                y=placement["y"],
                rotation=placement["rotation"],
                layer=placement["layer"],
                value="",  # Could add component values
            )
            footprint_lines.append(fp)
        sections.append("\n".join(footprint_lines))

        # Traces (segments)
        trace_lines = []
        for trace in self.layout.export_traces():
            net_num = self._get_net_number(trace["net"])
            seg = self._generate_segment(
                start=trace["start"],
                end=trace["end"],
                width=trace["width"],
                layer=trace["layer"],
                net=net_num,
            )
            trace_lines.append(seg)
        sections.append("\n".join(trace_lines))

        # Vias
        via_lines = []
        for block in self.layout.blocks.values():
            for via in block.vias:
                # Transform via position
                pos = via.position
                if block.rotation != 0:
                    pos = pos.rotate(block.rotation)
                pos = pos + block.origin

                net_num = self._get_net_number(via.net)
                v = self._generate_via(
                    x=pos.x,
                    y=pos.y,
                    size=via.size,
                    drill=via.drill,
                    layers=(via.layers[0].value, via.layers[1].value),
                    net=net_num,
                )
                via_lines.append(v)

        for via in self.layout.inter_block_vias:
            net_num = self._get_net_number(via.net)
            v = self._generate_via(
                x=via.position.x,
                y=via.position.y,
                size=via.size,
                drill=via.drill,
                layers=(via.layers[0].value, via.layers[1].value),
                net=net_num,
            )
            via_lines.append(v)

        if via_lines:
            sections.append("\n".join(via_lines))

        # Close
        sections.append(")")

        return "\n".join(sections)

    def write(self, filepath: str):
        """Write PCB to file."""
        content = self.generate()
        Path(filepath).write_text(content)
        print(f"Wrote PCB: {filepath}")

    def generate_placement_update(self) -> dict[str, tuple]:
        """
        Generate component positions for updating an existing PCB.

        Returns dict of {ref: (x, y, rotation)} for each component.
        """
        result = {}
        for placement in self.layout.export_placements():
            result[placement["ref"]] = (placement["x"], placement["y"], placement["rotation"])
        return result

    def generate_trace_segments(self) -> list[str]:
        """Generate trace segment S-expressions that can be appended to existing PCB."""
        segments = []
        for trace in self.layout.export_traces():
            net_num = self._get_net_number(trace["net"])
            seg = self._generate_segment(
                start=trace["start"],
                end=trace["end"],
                width=trace["width"],
                layer=trace["layer"],
                net=net_num,
            )
            segments.append(seg)
        return segments


def update_pcb_placements(source_pcb: str, layout: PCBLayout, output_pcb: str):
    """
    Update component positions in an existing PCB file based on block layout.

    This reads an existing PCB, updates the (at x y rotation) for each
    component that exists in the layout, and writes the result.

    Args:
        source_pcb: Path to existing KiCad PCB file
        layout: PCBLayout with desired component positions
        output_pcb: Path for output PCB file
    """
    # Get desired positions
    exporter = KiCadPCBExporter(layout)
    positions = exporter.generate_placement_update()

    # Read existing PCB
    content = Path(source_pcb).read_text()

    # For each component, find and update its position
    for ref, (x, y, rotation) in positions.items():
        # Pattern to find this component's footprint block and its position
        # Looking for: (property "Reference" "REF" ...) within a footprint
        # Then finding the (at X Y R) line above it

        # Find footprint containing this reference
        pattern = (
            rf'(\(footprint\s+"[^"]+"\s+.*?\(property\s+"Reference"\s+"{re.escape(ref)}".*?\))'
        )

        def update_position(match):
            footprint_text = match.group(1)
            # Update the (at ...) line within this footprint
            at_pattern = r"\(at\s+[\d.-]+\s+[\d.-]+(?:\s+[\d.-]+)?\)"
            new_at = f"(at {x:.4f} {y:.4f} {rotation})"
            updated = re.sub(at_pattern, new_at, footprint_text, count=1)
            return updated

        content = re.sub(pattern, update_position, content, flags=re.DOTALL)

    # Write result
    Path(output_pcb).write_text(content)
    print(f"Updated PCB placements: {output_pcb}")
    print(f"  Updated {len(positions)} components")


__all__ = ["KiCadPCBExporter", "update_pcb_placements"]
