"""Build the complete fictional figures with ReportLab's chart library."""
import argparse
import csv
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path
from xml.sax.saxutils import escape

from reportlab import Version
from reportlab.graphics import renderSVG
from reportlab.graphics.charts.barcharts import HorizontalBarChart
from reportlab.graphics.shapes import Drawing, Rect, String
from reportlab.lib.colors import HexColor

BLUE = '#285C8E'
PALE = '#DCE3E9'
INK = '#263442'


def pct(n, d):
    return str((Decimal(n) * 100 / Decimal(d)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP))


def read_source(path):
    with path.open(newline='') as f:
        rows = list(csv.DictReader(f))
    if len(rows) != 4 or len({r['batch_id'] for r in rows}) != 4:
        raise ValueError('This fixed teaching case requires four distinct batch rows.')
    if len({r['snapshot_utc'] for r in rows}) != 1:
        raise ValueError('Mixed snapshots are not comparable.')
    for r in rows:
        r['total_cards'] = int(r['total_cards'])
        r['checked_cards'] = int(r['checked_cards'])
        if not 0 <= r['checked_cards'] <= r['total_cards'] or r['total_cards'] <= 0:
            raise ValueError('Invalid count or undefined share; stop rather than guess.')
    return rows


def build(source, dest, variant, svg_only=False, metadata_label=""):
    rows = read_source(source)
    wrong = variant == 'wrong'
    prepared = []
    for r in rows:
        n, total = r['checked_cards'], r['total_cards']
        second = total if wrong else total - n
        prepared.append({
            **r,
            'remaining_cards' if not wrong else 'incorrect_remaining_series': second,
            'plotted_total': n + second,
            'checked_share_pct' if not wrong else 'wrong_checked_share_pct': pct(n, n + second),
        })
    csv_name = 'wrong-chart-data.csv' if wrong else 'chart-data.csv'
    if not svg_only:
        with (dest / csv_name).open('w', newline='') as f:
            w = csv.DictWriter(f, fieldnames=list(prepared[0]))
            w.writeheader()
            w.writerows(prepared)

    d = Drawing(960, 500)
    d.add(Rect(0, 0, 960, 500, fillColor=HexColor('#FFFFFF'), strokeColor=None))
    def text(x, y, s, size=12, color=INK, bold=False):
        d.add(String(x, y, s, fontName='Helvetica-Bold' if bold else 'Helvetica',
                     fontSize=size, fillColor=HexColor(color)))
    text(32, 462, 'Deliberately wrong: checked counted twice' if wrong else
         'Exhibition cards: checked and remaining', 24, bold=True)
    text(32, 435, 'Original fictional snapshot | 12 June 2030, 14:00 UTC | Unit: cards', 13)
    text(32, 411, 'Wrong series: Checked + Total, presented as Checked + Remaining.' if wrong else
         'Each bar is one batch: Checked + Remaining = Total.', 13)
    text(704, 380, 'Exact counts and checked share', 12, bold=True)
    chart = HorizontalBarChart()
    chart.x, chart.y, chart.width, chart.height = 160, 132, 480, 240
    chart.data = [
        [r['checked_cards'] for r in prepared[::-1]],
        [r['incorrect_remaining_series'] if wrong else r['remaining_cards'] for r in prepared[::-1]],
    ]
    chart.categoryAxis.categoryNames = [r['batch_id'] + '  ' + r['batch_label'] for r in rows[::-1]]
    chart.categoryAxis.style = 'stacked'
    chart.categoryAxis.labels.fontName = 'Helvetica'
    chart.categoryAxis.labels.fontSize = 13
    chart.categoryAxis.labels.dx = -10
    chart.categoryAxis.visibleTicks = 0
    chart.valueAxis.valueMin, chart.valueAxis.valueMax, chart.valueAxis.valueStep = 0, 32, 8
    chart.valueAxis.labels.fontName = 'Helvetica'
    chart.valueAxis.labels.fontSize = 12
    chart.valueAxis.visibleGrid = 1
    chart.valueAxis.gridStrokeColor = HexColor('#E5E9ED')
    chart.valueAxis.gridStrokeWidth = 0.5
    chart.valueAxis.strokeColor = HexColor(INK)
    chart.categoryAxis.strokeColor = HexColor(INK)
    chart.barWidth = 27
    chart.groupSpacing = 24
    for i, color in enumerate([BLUE, PALE]):
        chart.bars[i].fillColor = HexColor(color)
        chart.bars[i].strokeColor = HexColor('#647687')
        chart.bars[i].strokeWidth = 0.6
    d.add(chart)
    # Direct text remains readable for a zero-width segment and in grayscale.
    for i, r in enumerate(prepared):
        y = 342 - 60 * i
        second = r['incorrect_remaining_series'] if wrong else r['remaining_cards']
        share = r['wrong_checked_share_pct'] if wrong else r['checked_share_pct']
        text(704, y, f"{r['checked_cards']} checked | {second} remaining", 12)
        unit = 'plotted total' if wrong else 'cards total'
        text(704, y - 18, f"{r['plotted_total']} {unit} | {share}% checked", 11, '#526273')
    text(385, 88, 'Number of cards', 13)
    d.add(Rect(32, 49, 15, 11, fillColor=HexColor(BLUE), strokeColor=HexColor('#647687'), strokeWidth=0.6))
    text(54, 49, 'Checked (dark)', 12)
    d.add(Rect(204, 49, 15, 11, fillColor=HexColor(PALE), strokeColor=HexColor('#647687'), strokeWidth=0.6))
    text(226, 49, 'Remaining (light)', 12)
    text(32, 22, 'Teaching counterexample: values do not follow the supplied definition.' if wrong else
         'Source: original EXHIBIT-CARDS-01 CSV. Read with the complete table and definitions.', 11, '#526273')
    dest.mkdir(parents=True, exist_ok=True)
    # Root metadata is generated from this same input; drawing markup is untouched.
    snapshot = rows[0]['snapshot_utc']
    prefix = 'Deliberately wrong chart: ' if wrong else (metadata_label + ': ' if metadata_label else '')
    title = prefix + 'Exhibition cards checked and remaining; fictional snapshot ' + snapshot + '; unit: cards'
    details = []
    for r in prepared:
        second = r['incorrect_remaining_series'] if wrong else r['remaining_cards']
        share = r['wrong_checked_share_pct'] if wrong else r['checked_share_pct']
        state = 'incorrectly labelled remaining' if wrong else 'remaining'
        details.append(f"{r['batch_id']} {r['batch_label']}: {r['checked_cards']} checked, {second} {state}, "
                       f"{r['plotted_total']} plotted cards, {share}% checked")
    explanation = ('Deliberate teaching error: the light segment uses the whole Total as Remaining, '
                   'so checked cards are counted twice. These are wrong plotted values, not corrected counts. '
                   if wrong else 'Checked and remaining are disjoint parts of each batch total; shares use checked/total. ')
    description = ('Original fictional EXHIBIT-CARDS-01 snapshot at ' + snapshot + '. Unit: cards. '
                   'Four disjoint catalogue batches on a shared zero-based 0-32 card axis. '
                   + explanation + '; '.join(details) + '. Dark is Checked; light is Remaining. '
                   'Static SVG; the full source table and text alternative accompany the figure.')
    svg = renderSVG.drawToString(d)
    assert svg.count('<title>...</title>') == svg.count('<desc>...</desc>') == 1
    svg = svg.replace('<title>...</title>', '<title>' + escape(title) + '</title>', 1)
    svg = svg.replace('<desc>...</desc>', '<desc>' + escape(description) + '</desc>', 1)
    (dest / ('wrong-chart.svg' if wrong else 'correct-chart.svg')).write_text(svg, encoding='utf-8')


if __name__ == '__main__':
    ap = argparse.ArgumentParser()
    ap.add_argument('--source', type=Path, default=Path(__file__).with_name('source-data.csv'))
    ap.add_argument('--out', type=Path, default=Path(__file__).parent)
    ap.add_argument('--variant', choices=['both', 'correct', 'wrong'], default='both')
    ap.add_argument('--svg-only', action='store_true', help='Write only SVG; preserve CSV and PNG outputs.')
    ap.add_argument('--metadata-label', default='', help='Optional nonvisual input-variant label.')
    args = ap.parse_args()
    args.out.mkdir(parents=True, exist_ok=True)
    for v in ['wrong', 'correct'] if args.variant == 'both' else [args.variant]:
        build(args.source, args.out, v, args.svg_only, args.metadata_label)
    print(f'ReportLab {Version}: built {args.variant} from {args.source.name}')
