#!/usr/bin/env python3
"""Offline DLR Development Lifecycle extractor.

Reads one compact, verified source-snippet row per quarter and emits canonical
metric rows for the DLR Development Lifecycle total row. This is not a live PDF
parser; it is an offline parser seeded from audited primary-source snippets.
"""

from __future__ import annotations

import csv
import re
from decimal import Decimal
from pathlib import Path


BASE_DIR = Path(__file__).resolve().parent
DEFAULT_MANIFEST = BASE_DIR / "dlr_development_lifecycle_manifest.csv"
DEFAULT_OUTPUT = BASE_DIR / "dlr_development_lifecycle_rows.csv"

CANONICAL_FIELDS = [
    "company",
    "period",
    "metric_name",
    "raw_value",
    "normalized_value",
    "unit",
    "value_qualifier",
    "ownership_basis",
    "forecast_or_actual",
    "is_derived",
    "source_url",
    "source_location",
    "source_snippet",
    "snippet_support_status",
    "confidence",
    "caveat",
]

METRIC_ORDER = [
    "future_land_mw",
    "future_shell_mw",
    "construction_underway_mw",
    "preleased_percentage",
    "development_lifecycle_total_investment_100_usd_m",
]

METRIC_METADATA = {
    "future_land_mw": {
        "unit": "MW",
        "forecast_or_actual": "forecast_pipeline_estimate",
        "caveat": (
            "Land MW is stored separately from shell MW; expected capacity may "
            "differ from actual capacity developed."
        ),
    },
    "future_shell_mw": {
        "unit": "MW",
        "forecast_or_actual": "forecast_pipeline_estimate",
        "caveat": "Shell MW is stored separately from land MW.",
    },
    "construction_underway_mw": {
        "unit": "MW",
        "forecast_or_actual": "actual_development_snapshot",
        "caveat": "Actual table snapshot, not forecast.",
    },
    "preleased_percentage": {
        "unit": "percent",
        "forecast_or_actual": "actual_development_snapshot",
        "caveat": "Pre-leased percentage applies to construction MW.",
    },
    "development_lifecycle_total_investment_100_usd_m": {
        "unit": "USD_millions",
        "forecast_or_actual": "mixed_actual_forecast_investment",
        "caveat": "Source reports dollars in thousands; normalized to USD millions.",
    },
}


def parse_number(value: str) -> Decimal:
    return Decimal(value.replace(",", "").replace("$", "").replace("%", ""))


def decimal_to_text(value: Decimal) -> str:
    text = format(value.normalize(), "f")
    if "." in text:
        text = text.rstrip("0").rstrip(".")
    return text


def parse_capacity_snippet(snippet: str) -> dict[str, str]:
    pattern = re.compile(
        r"^Total\s+"
        r"(?P<land>[\d,]+)\s+"
        r"(?P<shell>[\d,]+)\s+"
        r"\$[\d,]+\s+"
        r"\$[\d,]+\s+"
        r"(?P<underway>[\d,]+)\s+"
        r"(?P<preleased>\d+%)$"
    )
    match = pattern.match(snippet.strip())
    if not match:
        raise ValueError(f"Unparseable Development Lifecycle total snippet: {snippet!r}")
    return match.groupdict()


def parse_investment_snippet(snippet: str) -> tuple[str, str]:
    money_values = re.findall(r"\$([\d,]+)", snippet)
    if len(money_values) < 3:
        raise ValueError(f"Expected at least three investment values: {snippet!r}")
    raw_thousands = money_values[-1]
    normalized_millions = parse_number(raw_thousands) / Decimal("1000")
    return raw_thousands, decimal_to_text(normalized_millions)


def canonical_rows(manifest_path: Path = DEFAULT_MANIFEST) -> list[dict[str, str]]:
    rows: list[dict[str, str]] = []
    with manifest_path.open(newline="", encoding="utf-8") as handle:
        reader = csv.DictReader(handle)
        for source in reader:
            parsed = parse_capacity_snippet(source["development_lifecycle_total_row_snippet"])
            investment_raw, investment_normalized = parse_investment_snippet(
                source["development_lifecycle_investment_snippet"]
            )
            values = {
                "future_land_mw": (parsed["land"], decimal_to_text(parse_number(parsed["land"]))),
                "future_shell_mw": (parsed["shell"], decimal_to_text(parse_number(parsed["shell"]))),
                "construction_underway_mw": (
                    parsed["underway"],
                    decimal_to_text(parse_number(parsed["underway"])),
                ),
                "preleased_percentage": (
                    parsed["preleased"],
                    decimal_to_text(parse_number(parsed["preleased"])),
                ),
                "development_lifecycle_total_investment_100_usd_m": (
                    investment_raw,
                    investment_normalized,
                ),
            }

            for metric_name in METRIC_ORDER:
                metadata = METRIC_METADATA[metric_name]
                raw_value, normalized_value = values[metric_name]
                source_snippet = (
                    source["development_lifecycle_investment_snippet"]
                    if metric_name == "development_lifecycle_total_investment_100_usd_m"
                    else source["development_lifecycle_total_row_snippet"]
                )
                rows.append(
                    {
                        "company": source["company"],
                        "period": source["period"],
                        "metric_name": metric_name,
                        "raw_value": raw_value,
                        "normalized_value": normalized_value,
                        "unit": metadata["unit"],
                        "value_qualifier": "exact",
                        "ownership_basis": "total_100_share",
                        "forecast_or_actual": metadata["forecast_or_actual"],
                        "is_derived": "false",
                        "source_url": source["source_url"],
                        "source_location": source["source_location"],
                        "source_snippet": source_snippet,
                        "snippet_support_status": source["snippet_support_status"],
                        "confidence": source["confidence"],
                        "caveat": metadata["caveat"],
                    }
                )
    return rows


def write_rows(rows: list[dict[str, str]], output_path: Path = DEFAULT_OUTPUT) -> None:
    with output_path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=CANONICAL_FIELDS)
        writer.writeheader()
        writer.writerows(rows)


def main() -> None:
    write_rows(canonical_rows())


if __name__ == "__main__":
    main()
