import copy
import hashlib
import json
import math
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
WEB = ROOT / 'data/website-site-review-2026-09-18'
DATE = '2026-09-18'
SOURCE = 'supply-path-review'


def load(path):
    return json.loads(path.read_text())


def write(path, data):
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2, ensure_ascii=False, allow_nan=False) + '\n')


def artifact(path, download=None):
    raw = path.read_bytes()
    result = dict(path=str(path.relative_to(ROOT)), sha256=hashlib.sha256(raw).hexdigest(), bytes=len(raw))
    if download:
        result['download'] = download
        target = ROOT / 'website/public' / download.lstrip('/')
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_bytes(raw)
    return result


def main():
    catalog = load(ROOT / 'data/website-cost-service-2026-09-18/catalog.json')
    prior = copy.deepcopy(catalog)
    cases = load(ROOT / 'data/portfolio-2035-2026-09-18/case-index.json')
    fixed = [row for row in cases if row['case']['id'] in ['2012-fixed', '2022-fixed']]
    assert len(fixed) == 2
    limits = 'Preliminary supply-offer brief from two fixed-charging weather cases (2011/12 and 2021/22), 2035 demand, up to 2 GW imports and a 90-winter-day Leibstadt outage of unestimated frequency. Perfect foresight, pooled hydro, no internal grid or probabilistic reserve margin. Not routine annual replacement energy, a final procurement volume or a guarantee of adequacy. No fixed-charging 2023/24 result is available in this edition.'
    claims = []
    for suffix, value, unit, definition, formula in [
        ('energy', math.ceil(max(row['winter_shortfall_gwh'] for row in fixed) / 100) / 10, 'TWh', 'Maximum remaining winter energy among the two archived fixed-charging stress cases, rounded upward to 0.1 TWh.', 'ceil(max(2555.6857744809754, 2258.7349184467917) / 100) / 10'),
        ('power', math.ceil(max(row['unserved_peak_gw'] for row in fixed) * 10) / 10, 'GW', 'Maximum remaining hourly power gap among the two archived fixed-charging stress cases, rounded upward to 0.1 GW.', 'ceil(max(2.28209526160838, 0.7046165785531138) * 10) / 10'),
    ]:
        claims.append(dict(id=f'plan-fixed-screen-{suffix}', label=f'2035 starting {suffix} requirement before contracted vehicle flexibility', value=value, unit=unit, status='scenario', period='2035 demand with 2011/12 and 2021/22 weather', definition=definition, formula=formula, limitation=limits, sources=['portfolio-2035-model', SOURCE], reviewedAt=DATE))
    get = lambda id: next(row['value'] for row in catalog['claims'] if row['id'] == id)
    claims.append(dict(id='demand-central-increase-2050', label='Electricity demand increase in the central 2050 case', value=get('central-demand-2050') - get('final-demand-2024'), unit='TWh/year', status='scenario', period='2024 observed baseline to conditional 2050 central case', definition='Additional annual end-use electricity, before grid losses, storage charging and net exports.', formula='93.36564180546513 − 57.512 = 35.85364180546513 TWh/year.', limitation='Inherits the central model assumptions. Additional data-centre, hydrogen, e-fuel and district-heat growth beyond sector assumptions is zero; aviation conversion is excluded. Not a forecast or generation-capacity requirement.', sources=['demand-model-v1', SOURCE], reviewedAt=DATE))
    dry = next(row for row in fixed if row['case']['id'] == '2022-fixed')
    claims.append(dict(id='plan-fixed-2022-summer', label='Additional summer electricity in the 2021/22 fixed-charging test', value=dry['unserved_gwh'] - dry['winter_shortfall_gwh'], unit='GWh', status='scenario', period='April–September portion of the 2021/22 weather case, 2035 demand', definition='Remaining annual electricity gap less its October–March winter portion.', formula='2306.1959870084297 − 2258.7349184467917 = 47.461068561638 GWh.', limitation=limits + ' This summer gap is outside the 2.6 TWh winter quantity.', sources=['portfolio-2035-model', SOURCE], reviewedAt=DATE))
    decisions = [
        ('demand', 'Use the central 93.4 TWh end-use case as the provisional 2050 reference. Lower demand requires the stated combination of efficiency and weaker activity; keep growth additions explicit.', ['demand-model-v1', 'demand-scenario-comparison', 'bfe-data-centres-2026'], 'Central/lower/higher 2050 scenario rows; displayed growth and efficiency inputs; data-centre summary.', 'No probability is assigned to the demand cases; the central value is a planning choice, not a forecast.'),
        ('winter', 'Prioritise safe nuclear, finite hydro and additional winter delivery while retaining trade. Solar and vehicle services complement this supply.', ['bfe-electricity-2025', 'portfolio-2035-model'], 'BFE table 5; twelve supply cases and partitions.', 'Seasonal trade is not a measurement of essential imports; the stress results apply to their stated future mixes.'),
        ('hydro', 'Support delivery of Grimsel 4, development of Grimselsee and a viable Mattmark winter-shift project for their distinct services. Compensate owners for contracted service.', ['kwo-grimsel4-investment', 'kwo-grimsel-milestones-2026', 'axpo-mattmark-project', 'owner-model-v1'], 'Project records and dates in visual-chapters hydro rows; owner water/opportunity-cost calculations.', 'Conditional project preference, not final permitting or a benefit-cost ranking. Stored energy, seasonal shift and power cannot be added as one energy quantity.'),
        ('storage', 'Use batteries for short-duration contracted services. Introduce smart charging before relying on bidirectional exports. Keep bulk seasonal lithium-battery storage outside the base plan.', ['cost-chapter-calculations', 'owner-model-v1', 'v2x-suisse-2024', 'portfolio-2035-model'], 'Battery cycling sensitivities; owner costs and trading benchmark; V2X report pages 3–5; matched vehicle cases.', 'The cycling example is hypothetical; rare reserve availability can still have value. No claim that every short-duration battery project is profitable.'),
        ('nuclear', 'Pursue Gösgen/Leibstadt extension and owner-led development of a first large reference-based reactor. Use 2047 as the provisional schedule reference, with a 2053-or-later fallback.', ['bfe-lto-2026-background', 'nuclear-delivery-calculations', 'historical-nuclear-selection'], 'Extension summary; delivery planning and delayed cases; historical site dossiers.', 'Editorial planning choice among illustrative schedules. No assigned probability, supplier commitment, present build permission or assured fleet build rate.'),
        ('beznau', 'Base the earlier supply programme on the announced 2032/2033 retirements. Reconsider further operation only if the owner presents a funded, safe alternative.', ['beznau-ensi-operating-horizon', 'beznau-axpo-decision-faq', 'nuclear-delivery-calculations'], 'ENSI licence horizon; Axpo beyond-2033 assessment; historical output.', 'No legal expiry is inferred. Owner intentions and safety findings can change; the replacement programme continues independently.'),
        ('costs', 'Prioritise viable existing-reactor extensions; pay hydro and reserve providers for defined services; obtain complete new-nuclear offers with bounded risk allocation.', ['bfe-lto-2026-background', 'cost-chapter-calculations', 'owner-model-v1'], 'Extension cases; cost ledger and comparison contract; owner economics.', 'A recommendation supported by bounded evidence; neither national total cost nor a household tariff has been established.'),
        ('2035', 'Choose the 26 GWp solar and remaining-reactor package for development. Seek extra supply options against the 2.6 TWh winter/2.3 GW fixed-charging stress screen, and retain the conditional V2G comparison.', ['portfolio-2035-model'], '2012-fixed and 2022-fixed; previous plan-procurement claims use V2G cases separately.', limits),
        ('strategy', 'Develop the earlier supply bridge and the new-nuclear programme together. Aim for annual net exports and resilience to restricted imports through commercially viable investments.', ['nuclear-delivery-calculations', 'portfolio-2035-model', 'owner-model-v1', 'bfe-monitoring-2025'], 'Programme schedules, import sensitivities, owner economics and monitoring targets.', 'The package is a proposed direction with quantified partial tests, not a completed least-cost or sovereign 2050 portfolio.'),
    ]
    findings = [dict(id=id, recommendation=text, sources=sources, locator=locator, limitation=limitation, status='editorial recommendation') for id, text, sources, locator, limitation in decisions]
    known = {row['id'] for row in catalog['sources']}
    for row in findings:
        assert set(row['sources']) <= known
    inputs = [artifact(ROOT / path) for path in ['data/portfolio-2035-2026-09-18/case-index.json', 'data/website-demand-comparison-2026-09-17/comparison.json', 'data/website-nuclear-delivery-2026-09-17/evidence.json', 'data/website-visual-chapters-2026-09-17/visuals.json']]
    evidence = dict(reviewedAt=DATE, findings=findings, claims=claims, fixed_case_ids=[row['case']['id'] for row in fixed], fixed_case_values=[dict(id=row['case']['id'], winter_shortfall_gwh=row['winter_shortfall_gwh'], unserved_peak_gw=row['unserved_peak_gw']) for row in fixed], inputs=inputs)
    write(WEB / 'evidence.json', evidence)
    files = [artifact(WEB / 'evidence.json', '/downloads/supply-path/evidence.json'), artifact(Path(__file__).resolve(), '/downloads/supply-path/build_website_site_review.py'), *inputs]
    catalog['sources'].append(dict(id=SOURCE, title='WattInfo proposed supply path: decisions and supporting calculations', publisher='WattInfo', kind='project-analysis', url='/downloads/supply-path/evidence.json', publicationDate=DATE, edition='Proposed priorities and fixed-charging supply screen, 18 September 2026', retrievedAt=DATE, reviewedAt=DATE, locator='Nine recommendations with source locators and limits; fixed_case_values; four derived claims', note='Editorial choices linked to catalogued research. The 2.6 TWh/2.3 GW screen covers two fixed-charging stress cases with 2 GW imports and a long reactor outage. The 2021/22 case also has a smaller summer gap. No final procurement margin, complete 2050 fleet cost or construction commitment is established.', artifacts=files))
    catalog['claims'] += claims
    for key in ['sources', 'claims']:
        assert catalog[key][:len(prior[key])] == prior[key]
    for path in [WEB / 'catalog.json', ROOT / 'website/src/data/catalog.json', ROOT / 'website/public/downloads/catalog.json']:
        write(path, catalog)
    print('Catalogue:', len(catalog['sources']), 'sources;', len(catalog['claims']), 'claims')


if __name__ == '__main__':
    main()
