"""
Native editable chart factory using python-pptx.
NO matplotlib / PNG insertion. All charts are native PowerPoint objects.
"""
from pptx.chart.data import ChartData, CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION, XL_TICK_MARK, XL_DATA_LABEL_POSITION
from pptx.enum.text import PP_ALIGN
from pptx.util import Emu, Pt
from pptx.dml.color import RGBColor

# Color palette — aligned with reference design theme YAML
C_BLUE = RGBColor(0x1E, 0x3A, 0x5F)       # primary
C_BLUE_DARK = RGBColor(0x1E, 0x3A, 0x5F)  # primary dark
C_ACCENT = RGBColor(0x10, 0xB9, 0x81)     # accent (growth)
C_ACCENT_NEG = RGBColor(0xEF, 0x44, 0x44) # accentNeg (decline)
C_ORANGE = RGBColor(0xED, 0x7D, 0x31)
C_GRAY = RGBColor(0x64, 0x74, 0x8B)       # secondary
C_GREEN = RGBColor(0x10, 0xB9, 0x81)
C_RED = RGBColor(0xEF, 0x44, 0x44)
C_TEXT = RGBColor(0x33, 0x33, 0x33)
C_GRID = RGBColor(0xD9, 0xD9, 0xD9)
C_WHITE = RGBColor(0xFF, 0xFF, 0xFF)

# Default series colors for multi-color charts like doughnut/pie
DEFAULT_COLORS = [
    RGBColor(0x1E, 0x3A, 0x5F),  # primary
    RGBColor(0x10, 0xB9, 0x81),  # accent
    RGBColor(0xED, 0x7D, 0x31),  # orange
    RGBColor(0x64, 0x74, 0x8B),  # secondary
    RGBColor(0xEF, 0x44, 0x44),  # red
    RGBColor(0x70, 0x70, 0x70),  # dark gray
    RGBColor(0x44, 0x72, 0xC4),  # indigo
    RGBColor(0x10, 0xB9, 0x81),  # accent2
]


def _apply_common_style(chart, show_legend=False, category_axis_title=None, value_axis_title=None):
    """Apply common styling to a chart. Safe for all chart types."""
    chart.has_title = False
    
    # Legend handling
    if show_legend:
        chart.has_legend = True
        chart.legend.position = XL_LEGEND_POSITION.BOTTOM
        chart.legend.include_in_layout = False
        chart.legend.font.size = Pt(10)
        chart.legend.font.name = '微软雅黑'
    else:
        chart.has_legend = False

    # Category axis (not all charts have one, e.g. DOUGHNUT)
    try:
        cat_axis = chart.category_axis
        if cat_axis:
            cat_axis.tick_labels.font.size = Pt(10)
            cat_axis.tick_labels.font.color.rgb = RGBColor(0x66, 0x66, 0x66)
            cat_axis.tick_labels.font.name = '微软雅黑'
            cat_axis.format.line.fill.background()
            try:
                cat_axis.major_tick_mark = XL_TICK_MARK.NONE
            except Exception:
                pass
            if category_axis_title:
                try:
                    cat_axis.has_title = True
                    cat_axis.axis_title.text_frame.text = category_axis_title
                    cat_axis.axis_title.text_frame.paragraphs[0].font.size = Pt(10)
                    cat_axis.axis_title.text_frame.paragraphs[0].font.name = '微软雅黑'
                    cat_axis.axis_title.text_frame.paragraphs[0].font.color.rgb = RGBColor(0x66, 0x66, 0x66)
                except Exception:
                    pass
    except Exception:
        pass

    # Value axis
    try:
        val_axis = chart.value_axis
        if val_axis:
            val_axis.tick_labels.font.size = Pt(10)
            val_axis.tick_labels.font.color.rgb = RGBColor(0x66, 0x66, 0x66)
            val_axis.tick_labels.font.name = '微软雅黑'
            val_axis.format.line.fill.background()
            try:
                val_axis.major_tick_mark = XL_TICK_MARK.NONE
            except Exception:
                pass
            try:
                if val_axis.has_major_gridlines:
                    val_axis.major_gridlines.format.line.color.rgb = C_GRID
                    val_axis.major_gridlines.format.line.width = Pt(0.75)
            except Exception:
                pass
            if value_axis_title:
                try:
                    val_axis.has_title = True
                    val_axis.axis_title.text_frame.text = value_axis_title
                    val_axis.axis_title.text_frame.paragraphs[0].font.size = Pt(10)
                    val_axis.axis_title.text_frame.paragraphs[0].font.name = '微软雅黑'
                    val_axis.axis_title.text_frame.paragraphs[0].font.color.rgb = RGBColor(0x66, 0x66, 0x66)
                except Exception:
                    pass
    except Exception:
        pass

    # Plot area & chart area background cleanup
    try:
        chart.plot_area.format.fill.background()
        chart.plot_area.format.line.fill.background()
    except Exception:
        pass
    try:
        chart.format.fill.background()
        chart.format.line.fill.background()
    except Exception:
        pass


def _apply_data_labels(series, show_value=True, show_percent=False, font_size=Pt(10),
                       position=XL_DATA_LABEL_POSITION.OUTSIDE_END, number_format=None):
    """Add data labels to a series with safe formatting."""
    series.has_data_labels = True
    for point in series.points:
        dl = point.data_label
        dl.font.size = font_size
        dl.font.color.rgb = C_TEXT
        dl.font.name = '微软雅黑'
        if show_percent and hasattr(dl, 'show_percent'):
            try:
                dl.show_percent = True
            except Exception:
                pass
        if show_value and hasattr(dl, 'show_value'):
            try:
                dl.show_value = True
            except Exception:
                pass
        try:
            dl.position = position
        except Exception:
            pass
        if number_format and hasattr(dl, 'number_format'):
            try:
                dl.number_format = number_format
            except Exception:
                pass


def _set_series_color(series, color):
    """Safely set series fill color."""
    try:
        series.format.fill.solid()
        series.format.fill.fore_color.rgb = color
    except Exception:
        pass


def add_column_chart(slide, categories, values, left, top, width, height,
                     series_name='数值', color=C_BLUE, show_data_labels=True,
                     second_series=None, second_color=C_ORANGE,
                     category_axis_title=None, value_axis_title=None):
    """Add a clustered column chart."""
    chart_data = ChartData()
    chart_data.categories = categories
    chart_data.add_series(series_name, values)
    if second_series:
        chart_data.add_series(second_series[0], second_series[1])

    chart = slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_CLUSTERED,
        left, top, width, height,
        chart_data
    ).chart

    _apply_common_style(chart, show_legend=bool(second_series),
                        category_axis_title=category_axis_title,
                        value_axis_title=value_axis_title)

    # Color first series
    _set_series_color(chart.series[0], color)
    if second_series and len(chart.series) > 1:
        _set_series_color(chart.series[1], second_color)

    if show_data_labels:
        _apply_data_labels(chart.series[0])
        if second_series and len(chart.series) > 1:
            _apply_data_labels(chart.series[1])

    return chart


def add_bar_chart(slide, categories, values, left, top, width, height,
                  series_name='数值', color=C_BLUE, reverse_order=True,
                  show_data_labels=True,
                  category_axis_title=None, value_axis_title=None):
    """Add a clustered bar chart (horizontal)."""
    chart_data = ChartData()
    chart_data.categories = categories
    chart_data.add_series(series_name, values)

    chart = slide.shapes.add_chart(
        XL_CHART_TYPE.BAR_CLUSTERED,
        left, top, width, height,
        chart_data
    ).chart

    _apply_common_style(chart, show_legend=False,
                        category_axis_title=category_axis_title,
                        value_axis_title=value_axis_title)

    if reverse_order:
        try:
            chart.category_axis.reverse_order = True
        except Exception:
            pass

    _set_series_color(chart.series[0], color)
    if show_data_labels:
        _apply_data_labels(chart.series[0], position=XL_DATA_LABEL_POSITION.OUTSIDE_END)

    return chart


def add_horizontal_bar_chart(slide, categories, values, left, top, width, height,
                             series_name='数值', color=C_BLUE, reverse_order=True,
                             show_data_labels=True, data_label_format=None,
                             category_axis_title=None, value_axis_title=None):
    """Add a horizontal bar chart (alias for add_bar_chart with enhanced defaults)."""
    return add_bar_chart(
        slide, categories, values, left, top, width, height,
        series_name=series_name, color=color, reverse_order=reverse_order,
        show_data_labels=show_data_labels,
        category_axis_title=category_axis_title,
        value_axis_title=value_axis_title
    )


def add_line_chart(slide, categories, values, left, top, width, height,
                   series_name='数值', color=C_BLUE, marker_size=7,
                   show_data_labels=False, second_series=None, second_color=C_ORANGE,
                   category_axis_title=None, value_axis_title=None):
    """Add a line chart with markers."""
    chart_data = ChartData()
    chart_data.categories = categories
    chart_data.add_series(series_name, values)
    if second_series:
        chart_data.add_series(second_series[0], second_series[1])

    chart = slide.shapes.add_chart(
        XL_CHART_TYPE.LINE_MARKERS,
        left, top, width, height,
        chart_data
    ).chart

    _apply_common_style(chart, show_legend=bool(second_series),
                        category_axis_title=category_axis_title,
                        value_axis_title=value_axis_title)

    chart.series[0].format.line.color.rgb = color
    chart.series[0].format.line.width = Pt(2.5)
    try:
        chart.series[0].marker.style = 1  # circle
        chart.series[0].marker.size = marker_size
    except Exception:
        pass

    if second_series and len(chart.series) > 1:
        chart.series[1].format.line.color.rgb = second_color
        chart.series[1].format.line.width = Pt(2.5)
        try:
            chart.series[1].marker.style = 1
            chart.series[1].marker.size = marker_size
        except Exception:
            pass

    if show_data_labels:
        _apply_data_labels(chart.series[0])

    return chart


def add_pie_chart(slide, categories, values, left, top, width, height,
                  colors=None, show_data_labels=True, show_legend=True, show_percent=True):
    """Add a pie chart with legend and data labels."""
    chart_data = ChartData()
    chart_data.categories = categories
    chart_data.add_series('占比', values)

    chart = slide.shapes.add_chart(
        XL_CHART_TYPE.PIE,
        left, top, width, height,
        chart_data
    ).chart

    _apply_common_style(chart, show_legend=show_legend)

    # Color each point individually
    series = chart.series[0]
    point_colors = colors if colors else DEFAULT_COLORS
    for i, point in enumerate(series.points):
        try:
            point.format.fill.solid()
            point.format.fill.fore_color.rgb = point_colors[i % len(point_colors)]
        except Exception:
            pass

    total = sum(values) if values else 0
    if show_data_labels:
        series.has_data_labels = True
        for i, point in enumerate(series.points):
            dl = point.data_label
            dl.font.size = Pt(9)
            dl.font.color.rgb = C_TEXT
            dl.font.name = '微软雅黑'
            val = values[i] if i < len(values) else 0
            pct = val / total * 100 if total else 0
            # Compact label: fit inside slice; show value+percent, omit long category name
            if pct >= 15:
                dl.text_frame.text = f'{val}单\n({pct:.1f}%)'
            else:
                dl.text_frame.text = f'{pct:.1f}%'
            dl.show_value = False
            dl.show_percent = False
            dl.show_category_name = False
            try:
                dl.position = XL_DATA_LABEL_POSITION.CENTER
            except Exception:
                pass

    return chart


def add_doughnut_chart(slide, categories, values, left, top, width, height,
                       colors=None, hole_size=0.5, show_data_labels=True,
                       show_legend=True, show_percent=True, ring_ratio=None):
    """Add a doughnut chart with legend and data labels.
    
    Args:
        ring_ratio: Alias for hole_size as a ratio (0.0-1.0). If provided, overrides hole_size.
    """
    if ring_ratio is not None:
        hole_size = ring_ratio
    
    chart_data = ChartData()
    chart_data.categories = categories
    chart_data.add_series('占比', values)

    chart = slide.shapes.add_chart(
        XL_CHART_TYPE.DOUGHNUT,
        left, top, width, height,
        chart_data
    ).chart

    _apply_common_style(chart, show_legend=show_legend)

    # Hole size
    try:
        if hasattr(chart.plots[0], 'hole_size'):
            chart.plots[0].hole_size = int(hole_size * 100)
    except Exception:
        pass

    # Color each point individually
    series = chart.series[0]
    point_colors = colors if colors else DEFAULT_COLORS
    for i, point in enumerate(series.points):
        try:
            point.format.fill.solid()
            point.format.fill.fore_color.rgb = point_colors[i % len(point_colors)]
        except Exception:
            pass

    total = sum(values) if values else 0
    if show_data_labels:
        series.has_data_labels = True
        for i, point in enumerate(series.points):
            dl = point.data_label
            dl.font.size = Pt(9)
            dl.font.color.rgb = C_TEXT
            dl.font.name = '微软雅黑'
            val = values[i] if i < len(values) else 0
            pct = val / total * 100 if total else 0
            # Compact label: fit inside slice; show value+percent, omit long category name
            if pct >= 15:
                dl.text_frame.text = f'{val}单\n({pct:.1f}%)'
            else:
                dl.text_frame.text = f'{pct:.1f}%'
            dl.show_value = False
            dl.show_percent = False
            dl.show_category_name = False
            try:
                dl.position = XL_DATA_LABEL_POSITION.CENTER
            except Exception:
                pass

    return chart


def add_funnel_chart(slide, categories, values, left, top, width, height,
                     colors=None, show_data_labels=True, show_percent=True):
    """Add a funnel-style chart using horizontal bar chart with reversed order.
    
    Displays stages from top to bottom with data labels showing quantity + percentage.
    """
    total = sum(values) if values else 0
    chart_data = ChartData()
    chart_data.categories = categories
    chart_data.add_series('数量', values)

    chart = slide.shapes.add_chart(
        XL_CHART_TYPE.BAR_CLUSTERED,
        left, top, width, height,
        chart_data
    ).chart

    _apply_common_style(chart, show_legend=False)

    # Reverse order so first category is at top
    try:
        chart.category_axis.reverse_order = True
    except Exception:
        pass

    # Color each point individually
    series = chart.series[0]
    point_colors = colors if colors else DEFAULT_COLORS
    for i, point in enumerate(series.points):
        try:
            point.format.fill.solid()
            point.format.fill.fore_color.rgb = point_colors[i % len(point_colors)]
        except Exception:
            pass

    if show_data_labels:
        series.has_data_labels = True
        for i, point in enumerate(series.points):
            dl = point.data_label
            dl.font.size = Pt(11)
            dl.font.color.rgb = C_TEXT
            dl.font.name = '微软雅黑'
            dl.show_value = True
            if show_percent and total > 0:
                pct = values[i] / total * 100
                # Custom label with value and percent
                dl.text_frame.text = f"{values[i]} ({pct:.1f}%)"
                dl.show_value = False
                dl.show_percent = False
            dl.position = XL_DATA_LABEL_POSITION.OUTSIDE_END

    return chart


def add_grouped_bar_chart(slide, categories, series_list, left, top, width, height,
                          colors=None, show_data_labels=True, show_legend=True,
                          category_axis_title=None, value_axis_title=None):
    """Add a grouped (clustered) column/bar chart with multiple series.
    
    Args:
        series_list: list of tuples [(series_name, values), ...]
        colors: list of RGBColor for each series
    """
    chart_data = ChartData()
    chart_data.categories = categories
    for name, vals in series_list:
        chart_data.add_series(name, vals)

    chart = slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_CLUSTERED,
        left, top, width, height,
        chart_data
    ).chart

    _apply_common_style(chart, show_legend=show_legend,
                        category_axis_title=category_axis_title,
                        value_axis_title=value_axis_title)

    point_colors = colors if colors else DEFAULT_COLORS
    for i, series in enumerate(chart.series):
        _set_series_color(series, point_colors[i % len(point_colors)])
        if show_data_labels:
            _apply_data_labels(series)

    return chart


def add_table(slide, rows, cols, data, left, top, width, height,
              header_color=RGBColor(0x2E, 0x5B, 0x8B),
              header_text_color=RGBColor(0xFF, 0xFF, 0xFF),
              cell_color=RGBColor(0xFF, 0xFF, 0xFF),
              alternate_color=RGBColor(0xF8, 0xFA, 0xFC),
              font_size=Pt(11),
              max_cell_chars=45):
    """
    Add a styled table with auto row height and text truncation.
    data: list of lists, first row is header.
    max_cell_chars: max characters per cell before truncating with '...'
    """
    from pptx.util import Pt
    table = slide.shapes.add_table(rows, cols, left, top, width, height).table

    # Set column widths proportionally
    for i in range(cols):
        table.columns[i].width = width // cols

    # Calculate row height based on content
    base_row_height = height // rows
    for r_idx, row_data in enumerate(data):
        for c_idx, val in enumerate(row_data):
            if c_idx >= cols:
                break
            cell = table.cell(r_idx, c_idx)
            text = str(val) if val is not None else ''
            # Truncate if too long
            if len(text) > max_cell_chars:
                text = text[:max_cell_chars - 1] + '...'
            cell.text = text

            # Enable word wrap
            cell.text_frame.word_wrap = True

            # Font styling
            paragraph = cell.text_frame.paragraphs[0]
            paragraph.font.size = font_size
            paragraph.font.name = '微软雅黑'
            paragraph.font.color.rgb = header_text_color if r_idx == 0 else C_TEXT

            # Background
            cell.fill.solid()
            if r_idx == 0:
                cell.fill.fore_color.rgb = header_color
                paragraph.font.bold = True
            else:
                if r_idx % 2 == 0 and alternate_color:
                    cell.fill.fore_color.rgb = alternate_color
                else:
                    cell.fill.fore_color.rgb = cell_color

            # Vertical alignment
            cell.vertical_anchor = 1  # middle

    return table


if __name__ == '__main__':
    from pptx import Presentation
    prs = Presentation()
    blank = prs.slide_layouts[6]
    s = prs.slides.add_slide(blank)

    add_column_chart(s, ['A', 'B', 'C'], [10, 20, 15],
                     Emu(1000000), Emu(1000000), Emu(5000000), Emu(3000000))
    add_bar_chart(s, ['X', 'Y', 'Z'], [30, 40, 25],
                  Emu(1000000), Emu(4500000), Emu(5000000), Emu(3000000))
    add_doughnut_chart(s, ['A', 'B', 'C'], [30, 40, 25],
                       Emu(1000000), Emu(8000000), Emu(3000000), Emu(2000000),
                       show_legend=True)
    add_pie_chart(s, ['A', 'B', 'C'], [30, 40, 25],
                  Emu(4500000), Emu(8000000), Emu(3000000), Emu(2000000),
                  show_legend=True)
    add_funnel_chart(s, ['Stage1', 'Stage2', 'Stage3'], [100, 60, 30],
                     Emu(1000000), Emu(11000000), Emu(5000000), Emu(3000000))
    add_grouped_bar_chart(s, ['A', 'B', 'C'], [
        ('本期', [10, 20, 15]),
        ('上期', [8, 18, 12])
    ], Emu(6000000), Emu(11000000), Emu(5000000), Emu(3000000))

    prs.save('chart_test.pptx')
    print('Saved chart_test.pptx')
