# Copyright 2026 Gaofeng Fan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
SVG export for analogpy schematics.

Generates SVG strings from Circuit and Testbench objects without
requiring reportlab or pypdf. Only needs schemdraw and matplotlib.

Usage:
    from analogpy.visualization.svg import render_schematic_svg

    svg_string = render_schematic_svg(my_circuit)

    # For testbench with all subcircuits:
    svg_dict = render_all_schematics_svg(my_testbench)
    # Returns: {"tb_name": "..svg..", "cell1": "..svg..", ...}
"""

import re
from typing import Dict, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from ..circuit import Circuit
    from ..testbench import Testbench


def _make_svg_responsive(svg: str) -> str:
    """Post-process SVG for responsive web embedding.

    - Strips XML declaration and DOCTYPE (not needed for inline SVG)
    - Replaces fixed width/height with width="100%" for responsive scaling
    - Keeps viewBox for proper aspect ratio
    """
    # Strip XML declaration and DOCTYPE
    svg = re.sub(r'<\?xml[^?]*\?>\s*', '', svg)
    svg = re.sub(r'<!DOCTYPE[^>]*>\s*', '', svg)

    # Replace fixed width/height with responsive width, remove height
    # to let viewBox control aspect ratio
    svg = re.sub(
        r'(<svg[^>]*)\s+width="[^"]*"',
        r'\1 width="100%"',
        svg
    )
    svg = re.sub(
        r'(<svg[^>]*)\s+height="[^"]*"',
        r'\1',
        svg
    )

    return svg.strip()


def drawing_to_svg(drawing, responsive: bool = True) -> str:
    """Convert a schemdraw Drawing object to an SVG string.

    Uses schemdraw's get_imagedata() for direct SVG export,
    falling back to saving to a temp file if needed.

    If the drawing has a ``_analogpy_inst_targets`` attribute (set by
    ``create_testbench_block_diagram`` / ``create_cell_schematic``), a
    second render pass is performed to inject transparent click-target
    rectangles with ``id="analogpy-inst-{name}"`` for each component.
    This enables bidirectional schematic↔editor selection in the browser.

    Args:
        drawing: A schemdraw.Drawing object.
        responsive: If True, make SVG responsive for web embedding
                    (width="100%", no fixed height).

    Returns:
        SVG content as a string.
    """
    import matplotlib
    matplotlib.rcParams['svg.fonttype'] = 'none'  # Text as text, not paths

    def _render(d):
        svg = None
        try:
            data = d.get_imagedata('svg')
            if isinstance(data, bytes):
                svg = data.decode('utf-8')
            else:
                svg = str(data)
        except Exception:
            pass

        # Fallback: save to temp file
        if svg is None:
            import tempfile, os
            fd, temp_path = tempfile.mkstemp(suffix='.svg')
            os.close(fd)
            try:
                d.save(temp_path)
                with open(temp_path, 'r') as f:
                    svg = f.read()
            finally:
                if os.path.exists(temp_path):
                    os.unlink(temp_path)
        return svg

    svg = _render(drawing)

    # Second pass: inject transparent click-target rects with component IDs
    inst_targets = getattr(drawing, '_analogpy_inst_targets', None)
    if inst_targets and svg:
        try:
            import matplotlib.pyplot as plt
            import matplotlib.patches as mpatches
            figs = [plt.figure(n) for n in plt.get_fignums()]
            axes = figs[-1].get_axes() if figs else []
            if axes:
                ax = axes[0]
                for name, (cx, cy, bw, bh) in inst_targets.items():
                    rect = mpatches.Rectangle(
                        (cx - bw / 2, cy - bh / 2), bw, bh,
                        linewidth=0, facecolor=(0, 0, 0, 0), edgecolor='none',
                    )
                    rect.set_gid(f'analogpy-inst-{name}')
                    ax.add_patch(rect)
                svg = _render(drawing)
        except Exception:
            pass  # Fall back to SVG without click targets

    if responsive and svg:
        svg = _make_svg_responsive(svg)

    # Tag net-name text elements with class="analogpy-net-{name}" for browser highlighting
    net_names = getattr(drawing, '_analogpy_net_names', None)
    if net_names and svg:
        import re
        for net_name in net_names:
            # Match <g id="text_N"> blocks whose <text> content is exactly this net name
            svg = re.sub(
                r'(<g id="text_\d+">)(\s*<text [^>]*>' + re.escape(net_name) + r'</text>\s*</g>)',
                r'<g class="analogpy-net-' + net_name + r'">\1\2</g>',
                svg
            )

    return svg.strip() if svg else ""


def render_schematic_svg(circuit, title: Optional[str] = None,
                         scale: float = 1.0, compact: bool = False) -> str:
    """Render a single circuit's schematic as an SVG string.

    Args:
        circuit: A Circuit or Testbench object.
        title: Optional title override.
        scale: Drawing scale factor (default 1.0).
        compact: If True, use tighter spacing (default False).

    Returns:
        SVG content as a string.
    """
    from .symbols import create_cell_schematic
    drawing = create_cell_schematic(circuit, title=title, scale=scale, compact=compact)
    return drawing_to_svg(drawing)


def render_block_diagram_svg(testbench, scale: float = 1.0) -> str:
    """Render a testbench block diagram as an SVG string.

    Args:
        testbench: A Testbench object.
        scale: Drawing scale factor (default 1.0).

    Returns:
        SVG content as a string.
    """
    from .symbols import create_testbench_block_diagram
    drawing = create_testbench_block_diagram(testbench, scale=scale)
    return drawing_to_svg(drawing)


def render_all_schematics_svg(ckt, depth: int = 100) -> Dict[str, str]:
    """Render schematics for a circuit/testbench and all its subcircuits.

    Returns a dict mapping circuit name to SVG string.
    The top-level circuit is included first.

    For a Testbench, a block diagram is also included under
    the key "{name}_block_diagram".

    Args:
        ckt: A Circuit or Testbench object.
        depth: Maximum recursion depth for subcircuits.

    Returns:
        Dict of {name: svg_string} for each circuit in the hierarchy.
    """
    from ..spectre import collect_subcircuits, is_builtin

    result: Dict[str, str] = {}

    # If testbench, include block diagram
    is_testbench = hasattr(ckt, 'analyses')
    if is_testbench:
        try:
            result[f"{ckt.name}_block_diagram"] = render_block_diagram_svg(ckt)
        except Exception:
            pass

    # Top-level schematic
    try:
        result[ckt.name] = render_schematic_svg(ckt)
    except Exception:
        pass

    # Subcircuit schematics (recursively collected, non-primitive only)
    if depth > 0:
        subcircuits = collect_subcircuits(ckt)
        for name, subckt in subcircuits.items():
            if not is_builtin(subckt):
                try:
                    result[name] = render_schematic_svg(subckt)
                except Exception:
                    pass

    return result
