import json
import logging
import re
from app.services.llm import call_llm_routed

logger = logging.getLogger(__name__)

CALCULATOR_SPEC_SCHEMA = {
    "type": "object",
    "required": ["id", "name", "description", "category", "inputs", "outputs", "formula_js", "preloaded_data"],
    "properties": {
        "id": {"type": "string", "description": "Unique kebab-case identifier"},
        "name": {"type": "string", "description": "Human-readable calculator name"},
        "description": {"type": "string", "description": "One-line description shown below the title"},
        "category": {"type": "string", "enum": ["universal", "niche_specific"]},
        "icon": {"type": "string", "description": "Emoji or icon identifier"},
        "inputs": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["id", "label", "type"],
                "properties": {
                    "id": {"type": "string"},
                    "label": {"type": "string"},
                    "type": {"type": "string", "enum": ["number", "currency", "percentage", "select", "range"]},
                    "placeholder": {"type": "string"},
                    "tooltip": {"type": "string"},
                    "default_value": {},
                    "min": {"type": "number"},
                    "max": {"type": "number"},
                    "step": {"type": "number"},
                    "options": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "value": {"type": "string"},
                                "label": {"type": "string"}
                            }
                        }
                    },
                    "prefix": {"type": "string"},
                    "suffix": {"type": "string"},
                    "grid_col": {"type": "string", "description": "Tailwind col-span class hint"}
                }
            }
        },
        "outputs": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["id", "label", "format"],
                "properties": {
                    "id": {"type": "string"},
                    "label": {"type": "string"},
                    "format": {"type": "string", "enum": ["currency", "percentage", "number", "text", "months"]},
                    "highlight": {"type": "boolean"},
                    "chart_bar": {"type": "boolean", "description": "If true, render as bar in CSS chart"}
                }
            }
        },
        "formula_js": {"type": "string", "description": "JavaScript function body that calculates outputs from inputs"},
        "preloaded_data": {
            "type": "object",
            "description": "Industry benchmarks, averages, standard rates embedded in the calculator"
        },
        "sources": {
            "type": "array",
            "items": {"type": "string"},
            "description": "Citation sources for preloaded data"
        },
        "layout_hints": {
            "type": "object",
            "properties": {
                "input_columns": {"type": "integer", "default": 2},
                "output_style": {"type": "string", "enum": ["cards", "table", "chart", "mixed"]},
                "show_chart": {"type": "boolean"},
                "chart_type": {"type": "string", "enum": ["bar", "horizontal_bar", "breakdown"]}
            }
        }
    }
}

DEFAULT_BRAND_COLORS = {
    "primary": "#4F46E5",
    "secondary": "#7C3AED",
    "accent": "#06B6D4"
}


def _safe_colors(brand_colors: dict = None) -> dict:
    if not brand_colors:
        return DEFAULT_BRAND_COLORS.copy()
    return {
        "primary": brand_colors.get("primary", brand_colors.get("color_primary", DEFAULT_BRAND_COLORS["primary"])),
        "secondary": brand_colors.get("secondary", brand_colors.get("color_secondary", DEFAULT_BRAND_COLORS["secondary"])),
        "accent": brand_colors.get("accent", brand_colors.get("color_accent", DEFAULT_BRAND_COLORS["accent"])),
    }


def generate_universal_calculators(niche_name: str, brand_colors: dict = None) -> list[dict]:
    colors = _safe_colors(brand_colors)
    niche_label = niche_name.replace("_", " ").title() if niche_name else "Business"

    roi_calculator = {
        "id": "roi-calculator",
        "name": "ROI Calculator",
        "description": f"Calculate your return on investment for your {niche_label} venture",
        "category": "universal",
        "icon": "📈",
        "inputs": [
            {
                "id": "initial_investment",
                "label": "Initial Investment",
                "type": "currency",
                "placeholder": "25000",
                "tooltip": "Total upfront capital required to launch — includes equipment, setup, licenses, and initial inventory",
                "default_value": 25000,
                "min": 0,
                "max": 10000000,
                "step": 500,
                "prefix": "$",
                "grid_col": "col-span-1"
            },
            {
                "id": "monthly_revenue",
                "label": "Expected Monthly Revenue",
                "type": "currency",
                "placeholder": "8000",
                "tooltip": "Projected gross monthly income from all revenue streams",
                "default_value": 8000,
                "min": 0,
                "max": 5000000,
                "step": 250,
                "prefix": "$",
                "grid_col": "col-span-1"
            },
            {
                "id": "monthly_costs",
                "label": "Monthly Operating Costs",
                "type": "currency",
                "placeholder": "3500",
                "tooltip": "Total recurring monthly expenses — rent, payroll, utilities, software, marketing, supplies",
                "default_value": 3500,
                "min": 0,
                "max": 2000000,
                "step": 100,
                "prefix": "$",
                "grid_col": "col-span-1"
            },
            {
                "id": "growth_rate",
                "label": "Monthly Growth Rate",
                "type": "percentage",
                "placeholder": "5",
                "tooltip": "Expected month-over-month revenue growth percentage. Industry average is 3-8%.",
                "default_value": 5,
                "min": 0,
                "max": 100,
                "step": 0.5,
                "suffix": "%",
                "grid_col": "col-span-1"
            }
        ],
        "outputs": [
            {"id": "monthly_profit", "label": "Monthly Profit", "format": "currency", "highlight": True, "chart_bar": True},
            {"id": "annual_roi", "label": "Annual ROI", "format": "percentage", "highlight": True, "chart_bar": False},
            {"id": "break_even_months", "label": "Break-Even Timeline", "format": "months", "highlight": True, "chart_bar": False},
            {"id": "month_3_revenue", "label": "Month 3 Revenue", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "month_6_revenue", "label": "Month 6 Revenue", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "month_9_revenue", "label": "Month 9 Revenue", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "month_12_revenue", "label": "Month 12 Revenue", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "year_1_total_profit", "label": "Year 1 Total Profit", "format": "currency", "highlight": True, "chart_bar": False}
        ],
        "formula_js": """
function calculate(inputs) {
    const inv = parseFloat(inputs.initial_investment) || 0;
    const rev = parseFloat(inputs.monthly_revenue) || 0;
    const costs = parseFloat(inputs.monthly_costs) || 0;
    const growth = (parseFloat(inputs.growth_rate) || 0) / 100;
    const monthlyProfit = rev - costs;
    let cumProfit = -inv;
    let breakEven = 0;
    let found = false;
    let totalProfit = 0;
    const projections = {};
    for (let m = 1; m <= 12; m++) {
        const mRev = rev * Math.pow(1 + growth, m - 1);
        const mProfit = mRev - costs;
        totalProfit += mProfit;
        cumProfit += mProfit;
        if (m === 3) projections.month_3_revenue = mRev;
        if (m === 6) projections.month_6_revenue = mRev;
        if (m === 9) projections.month_9_revenue = mRev;
        if (m === 12) projections.month_12_revenue = mRev;
        if (!found && cumProfit >= 0) { breakEven = m; found = true; }
    }
    if (!found) breakEven = inv > 0 && monthlyProfit > 0 ? Math.ceil(inv / monthlyProfit) : 0;
    const annualROI = inv > 0 ? ((totalProfit) / inv) * 100 : 0;
    return {
        monthly_profit: monthlyProfit,
        annual_roi: annualROI,
        break_even_months: breakEven,
        month_3_revenue: projections.month_3_revenue || rev,
        month_6_revenue: projections.month_6_revenue || rev,
        month_9_revenue: projections.month_9_revenue || rev,
        month_12_revenue: projections.month_12_revenue || rev,
        year_1_total_profit: totalProfit
    };
}""",
        "preloaded_data": {
            "avg_small_biz_roi_year1": "15-25%",
            "avg_startup_failure_rate": "20% in year 1",
            "avg_monthly_growth_rate_smb": "3-8%",
            "median_time_to_profitability": "6-18 months",
            "avg_operating_margin_by_industry": {
                "retail": "2-5%",
                "saas": "20-40%",
                "services": "15-25%",
                "ecommerce": "10-20%",
                "consulting": "25-50%"
            }
        },
        "sources": [
            "U.S. Bureau of Labor Statistics — Business Survival Rates",
            "SBA.gov — Small Business Financial Benchmarks",
            "SCORE — Startup Cost Averages 2024"
        ],
        "layout_hints": {
            "input_columns": 2,
            "output_style": "mixed",
            "show_chart": True,
            "chart_type": "bar"
        }
    }

    startup_cost_calculator = {
        "id": "startup-cost-estimator",
        "name": "Startup Cost Estimator",
        "description": f"Estimate total launch costs for your {niche_label} business",
        "category": "universal",
        "icon": "💰",
        "inputs": [
            {
                "id": "business_type",
                "label": "Business Type",
                "type": "select",
                "placeholder": "Select your business type",
                "tooltip": "The type of business determines typical startup cost ranges",
                "default_value": "online_service",
                "options": [
                    {"value": "online_service", "label": f"Online {niche_label} Service"},
                    {"value": "local_storefront", "label": f"Local {niche_label} Storefront"},
                    {"value": "hybrid", "label": f"Hybrid (Online + Local)"},
                    {"value": "ecommerce", "label": f"{niche_label} E-Commerce"},
                    {"value": "consulting", "label": f"{niche_label} Consulting/Agency"},
                    {"value": "saas", "label": f"{niche_label} SaaS/App"}
                ],
                "grid_col": "col-span-1"
            },
            {
                "id": "location_state",
                "label": "State / Region",
                "type": "select",
                "placeholder": "Select state",
                "tooltip": "Business registration and operating costs vary significantly by state",
                "default_value": "CA",
                "options": [
                    {"value": "CA", "label": "California"},
                    {"value": "TX", "label": "Texas"},
                    {"value": "NY", "label": "New York"},
                    {"value": "FL", "label": "Florida"},
                    {"value": "IL", "label": "Illinois"},
                    {"value": "WA", "label": "Washington"},
                    {"value": "CO", "label": "Colorado"},
                    {"value": "GA", "label": "Georgia"},
                    {"value": "NC", "label": "North Carolina"},
                    {"value": "OH", "label": "Ohio"},
                    {"value": "PA", "label": "Pennsylvania"},
                    {"value": "AZ", "label": "Arizona"},
                    {"value": "OTHER", "label": "Other State"}
                ],
                "grid_col": "col-span-1"
            },
            {
                "id": "entity_type",
                "label": "Business Entity Type",
                "type": "select",
                "placeholder": "Select entity type",
                "tooltip": "LLC is most popular for small businesses. Corp required for investors/fundraising.",
                "default_value": "llc",
                "options": [
                    {"value": "sole_prop", "label": "Sole Proprietorship"},
                    {"value": "llc", "label": "LLC"},
                    {"value": "s_corp", "label": "S-Corporation"},
                    {"value": "c_corp", "label": "C-Corporation"},
                    {"value": "partnership", "label": "Partnership"}
                ],
                "grid_col": "col-span-1"
            },
            {
                "id": "team_size",
                "label": "Initial Team Size",
                "type": "number",
                "placeholder": "1",
                "tooltip": "Number of people on the team at launch (including yourself). Affects payroll, tools, and workspace costs.",
                "default_value": 1,
                "min": 1,
                "max": 100,
                "step": 1,
                "grid_col": "col-span-1"
            }
        ],
        "outputs": [
            {"id": "total_startup_cost", "label": "Total Startup Cost", "format": "currency", "highlight": True, "chart_bar": False},
            {"id": "legal_costs", "label": "Legal & Registration", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "technology_costs", "label": "Technology & Hosting", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "marketing_costs", "label": "Marketing & Branding", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "tools_costs", "label": "Tools & Software", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "workspace_costs", "label": "Workspace & Equipment", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "misc_costs", "label": "Insurance & Misc", "format": "currency", "highlight": False, "chart_bar": True},
            {"id": "monthly_burn", "label": "Monthly Burn Rate", "format": "currency", "highlight": True, "chart_bar": False},
            {"id": "runway_months", "label": "Runway (at $50K reserve)", "format": "months", "highlight": True, "chart_bar": False}
        ],
        "formula_js": """
function calculate(inputs) {
    const btype = inputs.business_type || 'online_service';
    const state = inputs.location_state || 'CA';
    const entity = inputs.entity_type || 'llc';
    const team = parseInt(inputs.team_size) || 1;
    const entityCosts = {sole_prop: 50, llc: 250, s_corp: 800, c_corp: 1500, partnership: 300};
    const stateMult = {CA: 1.35, NY: 1.40, TX: 0.90, FL: 0.85, IL: 1.10, WA: 1.15, CO: 1.05, GA: 0.90, NC: 0.88, OH: 0.85, PA: 1.00, AZ: 0.88, OTHER: 1.00};
    const typeCosts = {
        online_service: {tech: 1200, marketing: 2500, tools: 800, workspace: 500, misc: 600},
        local_storefront: {tech: 2000, marketing: 4000, tools: 1500, workspace: 15000, misc: 3000},
        hybrid: {tech: 2500, marketing: 3500, tools: 1200, workspace: 8000, misc: 2000},
        ecommerce: {tech: 3000, marketing: 5000, tools: 2000, workspace: 1000, misc: 1500},
        consulting: {tech: 800, marketing: 2000, tools: 1500, workspace: 600, misc: 800},
        saas: {tech: 8000, marketing: 3000, tools: 3000, workspace: 1000, misc: 1200}
    };
    const sm = stateMult[state] || 1.0;
    const tc = typeCosts[btype] || typeCosts.online_service;
    const legal = Math.round(((entityCosts[entity] || 250) + 500 + (entity === 'c_corp' ? 2000 : 300)) * sm);
    const tech = Math.round(tc.tech * sm * (1 + (team - 1) * 0.15));
    const marketing = Math.round(tc.marketing * sm);
    const tools = Math.round(tc.tools * sm * (1 + (team - 1) * 0.3));
    const workspace = Math.round(tc.workspace * sm * (1 + (team - 1) * 0.25));
    const misc = Math.round(tc.misc * sm * (1 + (team - 1) * 0.1));
    const total = legal + tech + marketing + tools + workspace + misc;
    const monthlyBurn = Math.round((tech * 0.3 + tools + workspace * 0.08 + misc * 0.5 + team * 2500) * sm);
    const runway = monthlyBurn > 0 ? Math.round(50000 / monthlyBurn * 10) / 10 : 0;
    return {
        total_startup_cost: total,
        legal_costs: legal,
        technology_costs: tech,
        marketing_costs: marketing,
        tools_costs: tools,
        workspace_costs: workspace,
        misc_costs: misc,
        monthly_burn: monthlyBurn,
        runway_months: runway
    };
}""",
        "preloaded_data": {
            "avg_llc_filing_cost_by_state": {
                "CA": "$70 + $800 franchise tax",
                "TX": "$300",
                "NY": "$200 + $25 publication",
                "FL": "$125",
                "WA": "$200",
                "CO": "$50",
                "GA": "$100",
                "OH": "$99"
            },
            "avg_startup_costs_by_type": {
                "online_service": "$2,000 - $10,000",
                "local_storefront": "$25,000 - $75,000",
                "ecommerce": "$5,000 - $25,000",
                "saas": "$15,000 - $100,000",
                "consulting": "$2,000 - $8,000"
            },
            "typical_monthly_software_stack": {
                "domain_hosting": "$15-50/mo",
                "email_marketing": "$20-100/mo",
                "crm": "$25-150/mo",
                "accounting": "$15-70/mo",
                "project_management": "$10-30/mo"
            },
            "avg_time_to_first_revenue": "3-6 months for online, 1-3 months for local"
        },
        "sources": [
            "U.S. Small Business Administration — Startup Cost Guidelines",
            "SCORE.org — True Cost of Starting a Business 2024",
            "Secretary of State filings — LLC/Corp Registration Fees by State",
            "Clutch.co — Small Business Technology Spending Survey"
        ],
        "layout_hints": {
            "input_columns": 2,
            "output_style": "mixed",
            "show_chart": True,
            "chart_type": "breakdown"
        }
    }

    return [roi_calculator, startup_cost_calculator]


def generate_niche_calculators(niche_name: str, niche_data: dict, brand_colors: dict = None) -> list[dict]:
    colors = _safe_colors(brand_colors)
    niche_desc = niche_data.get("description", "") if niche_data else ""
    target_audience = niche_data.get("target_audience", "") if niche_data else ""
    monetization = niche_data.get("monetization_model", "") if niche_data else ""
    affiliates = niche_data.get("affiliate_programs", []) if niche_data else []
    synopsis = niche_data.get("synopsis", "") if niche_data else ""

    schema_str = json.dumps(CALCULATOR_SPEC_SCHEMA, indent=2)

    system_prompt = """You are a business calculator architect specializing in creating high-value interactive calculators for niche businesses. You produce perfectly structured JSON calculator specifications with real industry data, accurate formulas, and preloaded benchmarks that would take hours to research manually. You never produce generic or placeholder data — every number is researched and realistic."""

    prompt = f"""Generate exactly 2 niche-specific calculator specifications for the "{niche_name}" niche.

NICHE CONTEXT:
- Niche Name: {niche_name}
- Description: {niche_desc}
- Target Audience: {target_audience}
- Monetization Model: {monetization}
- Affiliate Programs: {json.dumps(affiliates)}
- Synopsis: {synopsis}

WHAT MAKES A CALCULATOR VALUABLE:
1. It saves the user HOURS of research by preloading industry-specific data (rates, averages, benchmarks)
2. It answers a SPECIFIC financial or operational question the target audience actually asks
3. It includes REAL numbers — industry averages, standard rates, typical ranges, not made-up values
4. The formula produces ACTIONABLE output — not just a single number, but a breakdown they can use for decisions

EXAMPLES OF GOOD CALCULATOR CONCEPTS:
- Pool Hall niche → "Tournament Prize Pool Calculator" (inputs: entry fee, player count, house cut %; outputs: total pool, winner payout, runner-up, house revenue. Preloaded: typical entry fees $20-$100, standard house cuts 10-30%, avg tournament sizes 16-64 players)
- Real Estate niche → "Mortgage Payment Calculator" (inputs: home price, down payment %, interest rate, loan term; outputs: monthly payment, total interest, amortization breakdown. Preloaded: current avg rates by loan type, typical down payments, regional price data)
- Fitness niche → "Membership Revenue Forecaster" (inputs: monthly fee, expected members, churn rate, growth rate; outputs: monthly/annual revenue, member lifetime value, projected 12-month growth. Preloaded: avg gym membership $40-60, industry churn 30-50%, avg member lifetime 4-8 months)

EXAMPLES OF BAD CALCULATOR CONCEPTS (DO NOT DO THIS):
- Generic "Profit Calculator" with no niche context
- Calculator with only 1-2 inputs and 1 output
- Calculator without preloaded data
- Calculator with fake/unrealistic numbers

REQUIREMENTS FOR EACH CALCULATOR:
1. Unique id in kebab-case
2. Clear, specific name related to the niche
3. 3-6 input fields with proper types (number, currency, percentage, select, range)
4. Every input MUST have: label, placeholder, tooltip, default_value
5. 4-8 output fields — mix of highlighted KPIs and chart bars
6. A complete JavaScript formula function that takes inputs object and returns outputs object
7. 5-10 preloaded data points with REAL industry benchmarks
8. 2-4 source citations
9. Layout hints

JSON SCHEMA TO FOLLOW:
{schema_str}

The formula_js field must contain a complete JavaScript function definition:
function calculate(inputs) {{ ... return {{ output_id: value, ... }}; }}

Return a JSON object with this exact structure:
{{
    "calculators": [
        {{ ...calculator spec 1... }},
        {{ ...calculator spec 2... }}
    ]
}}

CRITICAL: Return ONLY valid JSON. No markdown, no code fences, no explanation text. Every calculator must have category set to "niche_specific"."""

    try:
        response = call_llm_routed("site_copy", prompt, system_prompt, max_tokens=8192)
        cleaned = response.strip()
        if cleaned.startswith("```"):
            cleaned = cleaned.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
        start = cleaned.find("{")
        end = cleaned.rfind("}") + 1
        if start >= 0 and end > start:
            cleaned = cleaned[start:end]
        parsed = json.loads(cleaned)
        calculators = parsed.get("calculators", [])
        if not calculators and isinstance(parsed, list):
            calculators = parsed

        for calc in calculators:
            calc["category"] = "niche_specific"
            if "preloaded_data" not in calc or not calc["preloaded_data"]:
                calc["preloaded_data"] = {"note": "Industry benchmarks for " + niche_name}
            if "sources" not in calc:
                calc["sources"] = [f"Industry research — {niche_name} sector benchmarks"]
            if "layout_hints" not in calc:
                calc["layout_hints"] = {"input_columns": 2, "output_style": "mixed", "show_chart": True, "chart_type": "bar"}
            if "icon" not in calc:
                calc["icon"] = "🔢"

        if len(calculators) >= 2:
            return calculators[:2]

        logger.warning(f"AI returned {len(calculators)} calculators instead of 2 for niche '{niche_name}', padding with fallback")
        return _fallback_niche_calculators(niche_name, calculators)

    except Exception as e:
        logger.error(f"Failed to generate niche calculators for '{niche_name}': {e}")
        return _fallback_niche_calculators(niche_name, [])


def _fallback_niche_calculators(niche_name: str, existing: list) -> list[dict]:
    niche_label = niche_name.replace("_", " ").title()
    fallbacks = [
        {
            "id": f"{niche_name.lower().replace(' ', '-')}-revenue-projector",
            "name": f"{niche_label} Revenue Projector",
            "description": f"Project monthly and annual revenue for your {niche_label} business",
            "category": "niche_specific",
            "icon": "📊",
            "inputs": [
                {"id": "avg_transaction", "label": "Average Transaction Value", "type": "currency", "placeholder": "50", "tooltip": "Average amount per customer transaction", "default_value": 50, "min": 1, "max": 100000, "step": 5, "prefix": "$", "grid_col": "col-span-1"},
                {"id": "daily_customers", "label": "Daily Customers", "type": "number", "placeholder": "20", "tooltip": "Average number of customers per day", "default_value": 20, "min": 1, "max": 10000, "step": 1, "grid_col": "col-span-1"},
                {"id": "operating_days", "label": "Operating Days / Month", "type": "number", "placeholder": "26", "tooltip": "Number of days open per month", "default_value": 26, "min": 1, "max": 31, "step": 1, "grid_col": "col-span-1"},
                {"id": "profit_margin", "label": "Profit Margin", "type": "percentage", "placeholder": "25", "tooltip": "Net profit margin after all expenses", "default_value": 25, "min": 0, "max": 100, "step": 1, "suffix": "%", "grid_col": "col-span-1"}
            ],
            "outputs": [
                {"id": "monthly_revenue", "label": "Monthly Revenue", "format": "currency", "highlight": True, "chart_bar": True},
                {"id": "annual_revenue", "label": "Annual Revenue", "format": "currency", "highlight": True, "chart_bar": True},
                {"id": "monthly_profit", "label": "Monthly Profit", "format": "currency", "highlight": True, "chart_bar": True},
                {"id": "annual_profit", "label": "Annual Profit", "format": "currency", "highlight": True, "chart_bar": True}
            ],
            "formula_js": """function calculate(inputs) {
    const avg = parseFloat(inputs.avg_transaction) || 0;
    const daily = parseInt(inputs.daily_customers) || 0;
    const days = parseInt(inputs.operating_days) || 26;
    const margin = (parseFloat(inputs.profit_margin) || 0) / 100;
    const monthlyRev = avg * daily * days;
    return {
        monthly_revenue: monthlyRev,
        annual_revenue: monthlyRev * 12,
        monthly_profit: monthlyRev * margin,
        annual_profit: monthlyRev * 12 * margin
    };
}""",
            "preloaded_data": {"note": f"Typical {niche_label} transaction values range by market segment"},
            "sources": [f"Industry benchmarks — {niche_label} sector"],
            "layout_hints": {"input_columns": 2, "output_style": "mixed", "show_chart": True, "chart_type": "bar"}
        },
        {
            "id": f"{niche_name.lower().replace(' ', '-')}-pricing-optimizer",
            "name": f"{niche_label} Pricing Optimizer",
            "description": f"Find the optimal price point for your {niche_label} offerings",
            "category": "niche_specific",
            "icon": "🎯",
            "inputs": [
                {"id": "current_price", "label": "Current Price", "type": "currency", "placeholder": "99", "tooltip": "Your current price per unit/service", "default_value": 99, "min": 1, "max": 100000, "step": 1, "prefix": "$", "grid_col": "col-span-1"},
                {"id": "unit_cost", "label": "Cost Per Unit", "type": "currency", "placeholder": "35", "tooltip": "Your cost to deliver the product/service", "default_value": 35, "min": 0, "max": 100000, "step": 1, "prefix": "$", "grid_col": "col-span-1"},
                {"id": "monthly_volume", "label": "Monthly Volume", "type": "number", "placeholder": "100", "tooltip": "Current monthly units sold", "default_value": 100, "min": 1, "max": 1000000, "step": 1, "grid_col": "col-span-1"},
                {"id": "price_elasticity", "label": "Price Sensitivity", "type": "select", "placeholder": "Select sensitivity", "tooltip": "How much does demand change with price? Low = luxury/niche, High = commodity", "default_value": "medium", "options": [{"value": "low", "label": "Low (Luxury/Niche)"}, {"value": "medium", "label": "Medium (Standard)"}, {"value": "high", "label": "High (Commodity)"}], "grid_col": "col-span-1"}
            ],
            "outputs": [
                {"id": "current_margin", "label": "Current Margin", "format": "percentage", "highlight": True, "chart_bar": False},
                {"id": "optimal_price", "label": "Suggested Optimal Price", "format": "currency", "highlight": True, "chart_bar": False},
                {"id": "current_monthly_profit", "label": "Current Monthly Profit", "format": "currency", "highlight": False, "chart_bar": True},
                {"id": "optimized_monthly_profit", "label": "Optimized Monthly Profit", "format": "currency", "highlight": False, "chart_bar": True},
                {"id": "revenue_change", "label": "Revenue Change", "format": "percentage", "highlight": True, "chart_bar": False}
            ],
            "formula_js": """function calculate(inputs) {
    const price = parseFloat(inputs.current_price) || 0;
    const cost = parseFloat(inputs.unit_cost) || 0;
    const vol = parseInt(inputs.monthly_volume) || 0;
    const elasticity = inputs.price_elasticity || 'medium';
    const margin = price > 0 ? ((price - cost) / price) * 100 : 0;
    const eMult = {low: 1.25, medium: 1.12, high: 1.04};
    const volAdj = {low: 0.92, medium: 0.88, high: 0.78};
    const mult = eMult[elasticity] || 1.12;
    const vAdj = volAdj[elasticity] || 0.88;
    const optPrice = Math.round(price * mult * 100) / 100;
    const optVol = Math.round(vol * vAdj);
    const curProfit = (price - cost) * vol;
    const optProfit = (optPrice - cost) * optVol;
    const revChange = curProfit > 0 ? ((optProfit - curProfit) / curProfit) * 100 : 0;
    return {
        current_margin: margin,
        optimal_price: optPrice,
        current_monthly_profit: curProfit,
        optimized_monthly_profit: optProfit,
        revenue_change: revChange
    };
}""",
            "preloaded_data": {"pricing_strategies": {"cost_plus": "Add 50-100% markup", "value_based": "Price on perceived value", "competitive": "Match or undercut market"}},
            "sources": [f"Pricing strategy research — {niche_label} market"],
            "layout_hints": {"input_columns": 2, "output_style": "mixed", "show_chart": True, "chart_type": "bar"}
        }
    ]
    result = list(existing)
    for fb in fallbacks:
        if len(result) >= 2:
            break
        result.append(fb)
    return result[:2]


def render_calculator_html(spec: dict, brand_colors: dict = None) -> str:
    colors = _safe_colors(brand_colors)
    calc_id = spec.get("id", "calculator").replace(" ", "-")
    safe_id = re.sub(r'[^a-zA-Z0-9_-]', '', calc_id)
    fn_name = "calc_" + safe_id.replace("-", "_")

    name = _html_escape(spec.get("name", "Calculator"))
    description = _html_escape(spec.get("description", ""))
    icon = spec.get("icon", "🔢")
    inputs = spec.get("inputs", [])
    outputs = spec.get("outputs", [])
    formula_js = spec.get("formula_js", "function calculate(inputs) { return {}; }")
    preloaded = spec.get("preloaded_data", {})
    sources = spec.get("sources", [])
    layout = spec.get("layout_hints", {})
    input_cols = layout.get("input_columns", 2)
    show_chart = layout.get("show_chart", True)

    input_html = _render_inputs(inputs, safe_id, input_cols)
    output_html = _render_outputs(outputs, safe_id)
    chart_outputs = [o for o in outputs if o.get("chart_bar")]
    chart_html = _render_chart(chart_outputs, safe_id) if show_chart and chart_outputs else ""
    preloaded_html = _render_preloaded(preloaded)
    sources_html = _render_sources(sources)
    js_code = _render_js(formula_js, inputs, outputs, safe_id, fn_name, chart_outputs)

    return f'''<div id="{safe_id}-wrapper" class="calculator-wrapper" style="--brand-primary: {colors["primary"]}; --brand-secondary: {colors["secondary"]}; --brand-accent: {colors["accent"]};">
  <style>
    #{safe_id}-wrapper {{
      font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
      max-width: 720px;
      margin: 2rem auto;
    }}
    #{safe_id}-wrapper * {{ box-sizing: border-box; }}
    #{safe_id}-wrapper .calc-card {{
      background: rgba(255,255,255,0.05);
      backdrop-filter: blur(20px);
      -webkit-backdrop-filter: blur(20px);
      border: 1px solid rgba(255,255,255,0.12);
      border-radius: 1.25rem;
      padding: 2rem;
      box-shadow: 0 8px 32px rgba(0,0,0,0.12), 0 0 0 1px rgba(255,255,255,0.05) inset;
      transition: box-shadow 0.3s ease, transform 0.3s ease;
    }}
    #{safe_id}-wrapper .calc-card:hover {{
      box-shadow: 0 12px 48px rgba(0,0,0,0.18), 0 0 0 1px rgba(255,255,255,0.08) inset;
      transform: translateY(-2px);
    }}
    #{safe_id}-wrapper .calc-header {{
      text-align: center;
      margin-bottom: 1.75rem;
      padding-bottom: 1.25rem;
      border-bottom: 1px solid rgba(255,255,255,0.08);
    }}
    #{safe_id}-wrapper .calc-icon {{
      font-size: 2.5rem;
      margin-bottom: 0.5rem;
      display: block;
    }}
    #{safe_id}-wrapper .calc-title {{
      font-size: 1.5rem;
      font-weight: 700;
      color: var(--brand-primary);
      margin: 0 0 0.35rem 0;
      letter-spacing: -0.02em;
    }}
    #{safe_id}-wrapper .calc-desc {{
      font-size: 0.925rem;
      color: rgba(255,255,255,0.6);
      margin: 0;
    }}
    #{safe_id}-wrapper .calc-inputs {{
      display: grid;
      grid-template-columns: repeat({input_cols}, 1fr);
      gap: 1.25rem;
      margin-bottom: 1.5rem;
    }}
    @media (max-width: 640px) {{
      #{safe_id}-wrapper .calc-inputs {{ grid-template-columns: 1fr; }}
    }}
    #{safe_id}-wrapper .input-group {{
      display: flex;
      flex-direction: column;
      gap: 0.35rem;
    }}
    #{safe_id}-wrapper .input-label {{
      font-size: 0.8rem;
      font-weight: 600;
      color: rgba(255,255,255,0.75);
      display: flex;
      align-items: center;
      gap: 0.35rem;
      text-transform: uppercase;
      letter-spacing: 0.04em;
    }}
    #{safe_id}-wrapper .input-tooltip {{
      position: relative;
      cursor: help;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 16px;
      height: 16px;
      border-radius: 50%;
      background: rgba(255,255,255,0.1);
      font-size: 0.65rem;
      color: rgba(255,255,255,0.5);
      font-weight: 700;
      flex-shrink: 0;
    }}
    #{safe_id}-wrapper .input-tooltip:hover::after {{
      content: attr(data-tip);
      position: absolute;
      bottom: calc(100% + 8px);
      left: 50%;
      transform: translateX(-50%);
      background: rgba(15,15,25,0.95);
      color: rgba(255,255,255,0.9);
      padding: 0.5rem 0.75rem;
      border-radius: 0.5rem;
      font-size: 0.75rem;
      font-weight: 400;
      white-space: normal;
      width: 220px;
      text-transform: none;
      letter-spacing: 0;
      z-index: 100;
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
      border: 1px solid rgba(255,255,255,0.1);
      line-height: 1.4;
    }}
    #{safe_id}-wrapper .calc-input {{
      width: 100%;
      padding: 0.7rem 0.85rem;
      border: 1px solid rgba(255,255,255,0.12);
      border-radius: 0.6rem;
      background: rgba(255,255,255,0.04);
      color: rgba(255,255,255,0.9);
      font-size: 0.95rem;
      transition: border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
      outline: none;
      font-family: inherit;
    }}
    #{safe_id}-wrapper .calc-input:focus {{
      border-color: var(--brand-primary);
      box-shadow: 0 0 0 3px rgba(79,70,229,0.15);
      background: rgba(255,255,255,0.06);
    }}
    #{safe_id}-wrapper .calc-input:hover {{
      border-color: rgba(255,255,255,0.2);
    }}
    #{safe_id}-wrapper .input-addon {{
      position: relative;
    }}
    #{safe_id}-wrapper .input-prefix {{
      position: absolute;
      left: 0.85rem;
      top: 50%;
      transform: translateY(-50%);
      color: rgba(255,255,255,0.4);
      font-size: 0.9rem;
      pointer-events: none;
    }}
    #{safe_id}-wrapper .input-suffix {{
      position: absolute;
      right: 0.85rem;
      top: 50%;
      transform: translateY(-50%);
      color: rgba(255,255,255,0.4);
      font-size: 0.9rem;
      pointer-events: none;
    }}
    #{safe_id}-wrapper .has-prefix .calc-input {{ padding-left: 1.75rem; }}
    #{safe_id}-wrapper .has-suffix .calc-input {{ padding-right: 2rem; }}
    #{safe_id}-wrapper .calc-btn {{
      width: 100%;
      padding: 0.85rem 1.5rem;
      background: linear-gradient(135deg, var(--brand-primary), var(--brand-secondary));
      color: #fff;
      border: none;
      border-radius: 0.6rem;
      font-size: 1rem;
      font-weight: 600;
      cursor: pointer;
      transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
      letter-spacing: 0.01em;
      font-family: inherit;
    }}
    #{safe_id}-wrapper .calc-btn:hover {{
      transform: translateY(-1px);
      box-shadow: 0 6px 20px rgba(79,70,229,0.35);
    }}
    #{safe_id}-wrapper .calc-btn:active {{
      transform: translateY(0);
    }}
    #{safe_id}-wrapper .calc-results {{
      margin-top: 1.5rem;
      opacity: 0;
      transform: translateY(12px);
      transition: opacity 0.5s ease, transform 0.5s ease;
      pointer-events: none;
    }}
    #{safe_id}-wrapper .calc-results.visible {{
      opacity: 1;
      transform: translateY(0);
      pointer-events: auto;
    }}
    #{safe_id}-wrapper .results-title {{
      font-size: 0.8rem;
      text-transform: uppercase;
      letter-spacing: 0.06em;
      color: rgba(255,255,255,0.5);
      margin-bottom: 1rem;
      font-weight: 600;
    }}
    #{safe_id}-wrapper .results-grid {{
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
      gap: 0.85rem;
    }}
    #{safe_id}-wrapper .result-card {{
      background: rgba(255,255,255,0.04);
      border: 1px solid rgba(255,255,255,0.08);
      border-radius: 0.75rem;
      padding: 1rem;
      text-align: center;
      transition: border-color 0.3s ease, background 0.3s ease;
    }}
    #{safe_id}-wrapper .result-card:hover {{
      border-color: rgba(255,255,255,0.15);
      background: rgba(255,255,255,0.06);
    }}
    #{safe_id}-wrapper .result-card.highlight {{
      border-color: var(--brand-primary);
      background: rgba(79,70,229,0.08);
    }}
    #{safe_id}-wrapper .result-value {{
      font-size: 1.5rem;
      font-weight: 700;
      color: var(--brand-accent);
      margin-bottom: 0.2rem;
      letter-spacing: -0.02em;
    }}
    #{safe_id}-wrapper .result-card.highlight .result-value {{
      color: var(--brand-primary);
    }}
    #{safe_id}-wrapper .result-label {{
      font-size: 0.75rem;
      color: rgba(255,255,255,0.5);
      text-transform: uppercase;
      letter-spacing: 0.04em;
    }}
    #{safe_id}-wrapper .chart-container {{
      margin-top: 1.25rem;
      padding: 1rem;
      background: rgba(255,255,255,0.02);
      border-radius: 0.75rem;
      border: 1px solid rgba(255,255,255,0.06);
    }}
    #{safe_id}-wrapper .chart-bar-row {{
      display: flex;
      align-items: center;
      gap: 0.75rem;
      margin-bottom: 0.6rem;
    }}
    #{safe_id}-wrapper .chart-bar-label {{
      font-size: 0.75rem;
      color: rgba(255,255,255,0.55);
      min-width: 110px;
      text-align: right;
      flex-shrink: 0;
    }}
    #{safe_id}-wrapper .chart-bar-track {{
      flex: 1;
      height: 26px;
      background: rgba(255,255,255,0.04);
      border-radius: 0.4rem;
      overflow: hidden;
      position: relative;
    }}
    #{safe_id}-wrapper .chart-bar-fill {{
      height: 100%;
      border-radius: 0.4rem;
      background: linear-gradient(90deg, var(--brand-primary), var(--brand-accent));
      transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
      width: 0%;
      display: flex;
      align-items: center;
      justify-content: flex-end;
      padding-right: 0.5rem;
    }}
    #{safe_id}-wrapper .chart-bar-value {{
      font-size: 0.7rem;
      font-weight: 600;
      color: #fff;
      white-space: nowrap;
    }}
    #{safe_id}-wrapper .preloaded-section {{
      margin-top: 1.25rem;
      padding-top: 1rem;
      border-top: 1px solid rgba(255,255,255,0.06);
    }}
    #{safe_id}-wrapper .preloaded-toggle {{
      font-size: 0.8rem;
      color: var(--brand-primary);
      cursor: pointer;
      background: none;
      border: none;
      padding: 0.35rem 0;
      font-weight: 500;
      font-family: inherit;
      display: flex;
      align-items: center;
      gap: 0.35rem;
    }}
    #{safe_id}-wrapper .preloaded-toggle:hover {{ text-decoration: underline; }}
    #{safe_id}-wrapper .preloaded-content {{
      max-height: 0;
      overflow: hidden;
      transition: max-height 0.4s ease;
    }}
    #{safe_id}-wrapper .preloaded-content.open {{ max-height: 600px; }}
    #{safe_id}-wrapper .preloaded-list {{
      margin: 0.75rem 0 0 0;
      padding: 0;
      list-style: none;
      font-size: 0.8rem;
      color: rgba(255,255,255,0.5);
    }}
    #{safe_id}-wrapper .preloaded-list li {{
      padding: 0.3rem 0;
      border-bottom: 1px solid rgba(255,255,255,0.04);
      display: flex;
      justify-content: space-between;
    }}
    #{safe_id}-wrapper .preloaded-list li span:first-child {{ color: rgba(255,255,255,0.65); }}
    #{safe_id}-wrapper .sources-list {{
      margin: 0.5rem 0 0 0;
      padding: 0;
      list-style: none;
      font-size: 0.7rem;
      color: rgba(255,255,255,0.35);
    }}
    #{safe_id}-wrapper .sources-list li::before {{ content: "📎 "; }}
  </style>
  <div class="calc-card">
    <div class="calc-header">
      <span class="calc-icon">{icon}</span>
      <h3 class="calc-title">{name}</h3>
      <p class="calc-desc">{description}</p>
    </div>
    <div class="calc-inputs">
{input_html}
    </div>
    <button class="calc-btn" onclick="{fn_name}()">Calculate Results →</button>
    <div class="calc-results" id="{safe_id}-results">
      <div class="results-title">Your Results</div>
      <div class="results-grid" id="{safe_id}-results-grid">
{output_html}
      </div>
{chart_html}
    </div>
{preloaded_html}
{sources_html}
  </div>
  <script>
{js_code}
  </script>
</div>'''


def _html_escape(text: str) -> str:
    return (text
            .replace("&", "&amp;")
            .replace("<", "&lt;")
            .replace(">", "&gt;")
            .replace('"', "&quot;")
            .replace("'", "&#x27;"))


def _render_inputs(inputs: list, safe_id: str, cols: int) -> str:
    lines = []
    for inp in inputs:
        inp_id = f"{safe_id}-{inp['id']}"
        label = _html_escape(inp.get("label", inp["id"]))
        tooltip = _html_escape(inp.get("tooltip", ""))
        placeholder = _html_escape(str(inp.get("placeholder", "")))
        default = inp.get("default_value", "")
        prefix = inp.get("prefix", "")
        suffix = inp.get("suffix", "")
        inp_type = inp.get("type", "number")

        tooltip_html = f'<span class="input-tooltip" data-tip="{tooltip}">?</span>' if tooltip else ""

        addon_cls = ""
        if prefix:
            addon_cls += " has-prefix"
        if suffix:
            addon_cls += " has-suffix"

        lines.append(f'      <div class="input-group">')
        lines.append(f'        <label class="input-label" for="{inp_id}">{label} {tooltip_html}</label>')

        if inp_type == "select":
            options = inp.get("options", [])
            opts_html = "".join(
                f'<option value="{_html_escape(str(o.get("value", "")))}"'
                f'{" selected" if str(o.get("value", "")) == str(default) else ""}>'
                f'{_html_escape(o.get("label", o.get("value", "")))}</option>'
                for o in options
            )
            lines.append(f'        <select id="{inp_id}" class="calc-input">{opts_html}</select>')
        elif inp_type == "range":
            mn = inp.get("min", 0)
            mx = inp.get("max", 100)
            step = inp.get("step", 1)
            lines.append(f'        <input type="range" id="{inp_id}" class="calc-input" min="{mn}" max="{mx}" step="{step}" value="{default}">')
        else:
            mn = inp.get("min", "")
            mx = inp.get("max", "")
            step = inp.get("step", "")
            min_attr = f' min="{mn}"' if mn != "" else ""
            max_attr = f' max="{mx}"' if mx != "" else ""
            step_attr = f' step="{step}"' if step != "" else ""
            lines.append(f'        <div class="input-addon{addon_cls}">')
            if prefix:
                lines.append(f'          <span class="input-prefix">{_html_escape(prefix)}</span>')
            lines.append(f'          <input type="number" id="{inp_id}" class="calc-input" placeholder="{placeholder}" value="{default}"{min_attr}{max_attr}{step_attr}>')
            if suffix:
                lines.append(f'          <span class="input-suffix">{_html_escape(suffix)}</span>')
            lines.append(f'        </div>')

        lines.append(f'      </div>')
    return "\n".join(lines)


def _render_outputs(outputs: list, safe_id: str) -> str:
    lines = []
    for out in outputs:
        if out.get("chart_bar") and not out.get("highlight"):
            continue
        hl = " highlight" if out.get("highlight") else ""
        out_id = f"{safe_id}-out-{out['id']}"
        label = _html_escape(out.get("label", out["id"]))
        lines.append(f'        <div class="result-card{hl}">')
        lines.append(f'          <div class="result-value" id="{out_id}">—</div>')
        lines.append(f'          <div class="result-label">{label}</div>')
        lines.append(f'        </div>')
    return "\n".join(lines)


def _render_chart(chart_outputs: list, safe_id: str) -> str:
    lines = ['      <div class="chart-container" id="' + safe_id + '-chart">']
    for out in chart_outputs:
        bar_id = f"{safe_id}-bar-{out['id']}"
        val_id = f"{safe_id}-barval-{out['id']}"
        label = _html_escape(out.get("label", out["id"]))
        lines.append(f'        <div class="chart-bar-row">')
        lines.append(f'          <span class="chart-bar-label">{label}</span>')
        lines.append(f'          <div class="chart-bar-track">')
        lines.append(f'            <div class="chart-bar-fill" id="{bar_id}">')
        lines.append(f'              <span class="chart-bar-value" id="{val_id}"></span>')
        lines.append(f'            </div>')
        lines.append(f'          </div>')
        lines.append(f'        </div>')
    lines.append('      </div>')
    return "\n".join(lines)


def _render_preloaded(preloaded: dict) -> str:
    if not preloaded:
        return ""
    lines = [
        '    <div class="preloaded-section">',
        '      <button class="preloaded-toggle" onclick="this.nextElementSibling.classList.toggle(\'open\')">📊 Industry Benchmarks ▾</button>',
        '      <div class="preloaded-content">',
        '        <ul class="preloaded-list">'
    ]
    _flatten_preloaded(preloaded, lines)
    lines.append('        </ul>')
    lines.append('      </div>')
    lines.append('    </div>')
    return "\n".join(lines)


def _flatten_preloaded(data, lines, prefix=""):
    if isinstance(data, dict):
        for k, v in data.items():
            label = k.replace("_", " ").title()
            if prefix:
                label = f"{prefix} › {label}"
            if isinstance(v, dict):
                _flatten_preloaded(v, lines, label)
            elif isinstance(v, list):
                lines.append(f'          <li><span>{_html_escape(label)}</span><span>{_html_escape(", ".join(str(x) for x in v))}</span></li>')
            else:
                lines.append(f'          <li><span>{_html_escape(label)}</span><span>{_html_escape(str(v))}</span></li>')
    elif isinstance(data, str):
        lines.append(f'          <li><span>{_html_escape(prefix or "Info")}</span><span>{_html_escape(data)}</span></li>')


def _render_sources(sources: list) -> str:
    if not sources:
        return ""
    items = "".join(f'<li>{_html_escape(s)}</li>' for s in sources)
    return f'    <ul class="sources-list">{items}</ul>'


def _render_js(formula_js: str, inputs: list, outputs: list, safe_id: str, fn_name: str, chart_outputs: list) -> str:
    gather_lines = []
    for inp in inputs:
        inp_id = f"{safe_id}-{inp['id']}"
        gather_lines.append(f'      inputs["{inp["id"]}"] = document.getElementById("{inp_id}").value;')

    update_lines = []
    for out in outputs:
        out_id = f"{safe_id}-out-{out['id']}"
        fmt = out.get("format", "number")
        if fmt == "currency":
            update_lines.append(f'      el = document.getElementById("{out_id}"); if(el) el.textContent = "$" + Math.round(r["{out["id"]}"] || 0).toLocaleString();')
        elif fmt == "percentage":
            update_lines.append(f'      el = document.getElementById("{out_id}"); if(el) el.textContent = (Math.round((r["{out["id"]}"] || 0) * 10) / 10) + "%";')
        elif fmt == "months":
            update_lines.append(f'      el = document.getElementById("{out_id}"); if(el) {{ var v = r["{out["id"]}"] || 0; el.textContent = v < 1 ? "< 1 month" : Math.round(v) + (Math.round(v) === 1 ? " month" : " months"); }}')
        else:
            update_lines.append(f'      el = document.getElementById("{out_id}"); if(el) el.textContent = Math.round(r["{out["id"]}"] || 0).toLocaleString();')

    chart_update = ""
    if chart_outputs:
        bar_lines = []
        bar_lines.append(f'      var chartVals = [{", ".join("r[" + json.dumps(o["id"]) + "] || 0" for o in chart_outputs)}];')
        bar_lines.append(f'      var maxVal = Math.max.apply(null, chartVals.map(Math.abs)) || 1;')
        for i, out in enumerate(chart_outputs):
            bar_id = f"{safe_id}-bar-{out['id']}"
            val_id = f"{safe_id}-barval-{out['id']}"
            fmt = out.get("format", "number")
            if fmt == "currency":
                val_expr = '"$" + Math.round(Math.abs(chartVals[' + str(i) + '])).toLocaleString()'
            elif fmt == "percentage":
                val_expr = '(Math.round(chartVals[' + str(i) + '] * 10) / 10) + "%"'
            else:
                val_expr = 'Math.round(chartVals[' + str(i) + ']).toLocaleString()'
            bar_lines.append(f'      document.getElementById("{bar_id}").style.width = Math.max(2, Math.abs(chartVals[{i}]) / maxVal * 100) + "%";')
            bar_lines.append(f'      document.getElementById("{val_id}").textContent = {val_expr};')
        chart_update = "\n".join(bar_lines)

    return f"""    {formula_js}
    function {fn_name}() {{
      var inputs = {{}};
{chr(10).join(gather_lines)}
      var r = calculate(inputs);
      var el;
{chr(10).join(update_lines)}
{chart_update}
      document.getElementById("{safe_id}-results").classList.add("visible");
    }}"""


def generate_all_calculators(niche_name: str, niche_data: dict, brand_colors: dict = None) -> dict:
    colors = _safe_colors(brand_colors)

    universal = generate_universal_calculators(niche_name, colors)
    niche_specific = generate_niche_calculators(niche_name, niche_data, colors)

    all_specs = universal + niche_specific

    html_parts = []
    rendered = {}
    for spec in all_specs:
        html = render_calculator_html(spec, colors)
        rendered[spec["id"]] = html
        html_parts.append(html)

    html_bundle = "\n\n".join(html_parts)

    return {
        "universal": universal,
        "niche_specific": niche_specific,
        "html_bundle": html_bundle,
        "specs": all_specs,
        "rendered": rendered
    }
