"""Progressive form fill tools — return framework-specific graph8 form templates."""

from __future__ import annotations

import json
from typing import Any

from mcp.server import FastMCP

from g8_mcp_server.client import G8Client, format_result

SUPPORTED_FRAMEWORKS = {
    "html": "HTML / Vanilla JS",
    "react": "React",
    "nextjs": "Next.js",
    "vue": "Vue",
    "wordpress": "WordPress",
    "webflow": "Webflow",
    "shopify": "Shopify",
}

SUPPORTED_VARIANTS = {"embedded", "popup"}

DEFAULT_FIELD_STAGES: list[dict[str, Any]] = [
    {
        "stage": 1,
        "fields": [
            {"name": "email", "type": "email", "label": "Work Email", "required": True},
            {"name": "first_name", "type": "text", "label": "First Name", "required": True},
        ],
    },
    {
        "stage": 2,
        "fields": [
            {"name": "company", "type": "text", "label": "Company", "required": True},
            {"name": "job_title", "type": "text", "label": "Job Title", "required": False},
        ],
    },
    {
        "stage": 3,
        "fields": [
            {
                "name": "budget_range",
                "type": "select",
                "label": "Budget Range",
                "required": False,
                "options": ["<$10k", "$10k-$50k", "$50k-$100k", "$100k+"],
            },
            {
                "name": "timeline",
                "type": "select",
                "label": "Timeline",
                "required": False,
                "options": ["Immediate", "1-3 months", "3-6 months", "6+ months"],
            },
        ],
    },
]

_DATA_FLOW = (
    "Data flow to graph8 CDP:\n"
    "1. User fills form fields and submits\n"
    "2. JavaScript calls g8.identify(email, {all_collected_fields})\n"
    "3. JavaScript calls g8.track('form_submitted', {stage, field_values})\n"
    "4. Events flow through graph8 tracking pipeline (p.js -> Flow)\n"
    "5. Contact is created or updated in graph8 CDP\n"
    "6. Contact is available for sequences, campaigns, and enrichment"
)

_CUSTOMIZATION_GUIDE = (
    "- Modify FIELD_STAGES to change which fields appear at each stage\n"
    "- Add CSS classes to style the form to match your site design\n"
    "- Change the thank-you message in the completion block\n"
    "- For popup: adjust TRIGGER values (timeOnPage, scrollDepth, exitIntent, dismissDuration)\n"
    "- Add validation by checking field values before g8.identify()\n"
    "- The form automatically remembers progress across page loads via localStorage"
)

_PREREQUISITE = (
    "REQUIRED: The graph8 tracking snippet (p.js) must be loaded on the page "
    "before this form will work. Install it first:\n"
    "  MCP: call g8_get_tracking_snippet with your framework\n"
    "  CLI: run `g8 snippet --framework <your-framework>`\n"
    "Without the snippet, g8.identify() and g8.track() will not execute "
    "and form submissions will not reach graph8 CDP."
)


def register(server: FastMCP, client: G8Client) -> None:
    """Register form tools on the MCP server."""

    @server.tool()
    async def g8_get_form_template(
        framework: str,
        variant: str,
        field_stages: str | None = None,
        repo_id: str | None = None,
    ) -> str:
        """Return a progressive form fill template for the specified framework.

        PREREQUISITE: The graph8 tracking snippet (p.js) MUST be installed
        first. Run g8_get_tracking_snippet before using this tool. Without
        the snippet, g8.identify() and g8.track() calls in the form will
        silently fail and no data will reach graph8 CDP.

        Progressive forms collect visitor data across multiple visits:
        - Stage 1 (first visit): email + first name
        - Stage 2 (return visit): company + job title
        - Stage 3 (later visit): budget + timeline
        Each submission calls g8.identify() to update the contact in CDP
        and g8.track('form_submitted') for analytics.

        The form template is a complete, working component. Adapt it to
        match the site's design, modify field stages, or add to existing
        forms. The progressive logic uses localStorage to track stage.

        Args:
            framework: Target framework — html, react, nextjs, vue,
                       wordpress, webflow, or shopify
            variant: Form variant — embedded (inline on page) or popup
                     (overlay with time/scroll/exit-intent triggers)
            field_stages: Optional JSON string defining custom field stages.
                         Each stage has a stage number and array of fields.
                         Omit to use sensible defaults (email/name → company/
                         title → budget/timeline).
            repo_id: Optional repository ID for repo-specific write key
        """
        fw = framework.lower().strip()
        var = variant.lower().strip()

        error = _validate_inputs(fw, var)
        if error:
            return format_result(error)

        write_key, tracking_host = await _fetch_config(client, repo_id)
        stages = _parse_field_stages(field_stages)
        if isinstance(stages, dict) and "error" in stages:
            return format_result(stages)

        template_fn = _TEMPLATE_REGISTRY[(fw, var)]
        template_code = template_fn(write_key, tracking_host, stages)

        return format_result(_build_result(
            write_key, tracking_host, fw, var, template_code, stages,
        ))


# ---------------------------------------------------------------------------
# Input validation and config fetching
# ---------------------------------------------------------------------------


def _validate_inputs(fw: str, var: str) -> dict | None:
    """Return an error dict if framework or variant is invalid, else None."""
    if fw not in SUPPORTED_FRAMEWORKS:
        supported = ", ".join(sorted(SUPPORTED_FRAMEWORKS))
        return {"error": f"Unsupported framework '{fw}'", "supported_frameworks": supported}
    if var not in SUPPORTED_VARIANTS:
        supported = ", ".join(sorted(SUPPORTED_VARIANTS))
        return {"error": f"Unsupported variant '{var}'", "supported_variants": supported}
    return None


async def _fetch_config(client: G8Client, repo_id: str | None) -> tuple[str, str]:
    """Fetch write_key and tracking_host from the API."""
    if repo_id:
        data = await client.get(f"/repos/{repo_id}/snippet")
    else:
        data = await client.get("/snippet")
    payload = data.get("data", data)
    return payload.get("write_key", ""), payload.get("tracking_host", "")


def _parse_field_stages(raw: str | None) -> list[dict] | dict:
    """Parse field_stages JSON string or return defaults."""
    if not raw:
        return DEFAULT_FIELD_STAGES
    try:
        parsed = json.loads(raw)
        if not isinstance(parsed, list):
            return {"error": "field_stages must be a JSON array of stage objects"}
        return parsed
    except json.JSONDecodeError as exc:
        return {"error": f"Invalid field_stages JSON: {exc}"}


def _build_result(
    write_key: str,
    tracking_host: str,
    fw: str,
    var: str,
    template_code: str,
    stages: list[dict],
) -> dict:
    """Assemble the final result dictionary."""
    return {
        "write_key": write_key,
        "tracking_host": tracking_host,
        "framework": SUPPORTED_FRAMEWORKS[fw],
        "variant": var,
        "template_code": template_code,
        "field_stages": stages,
        "data_flow": _DATA_FLOW,
        "customization_guide": _CUSTOMIZATION_GUIDE,
        "prerequisite": _PREREQUISITE,
    }


# ---------------------------------------------------------------------------
# Shared JS helpers
# ---------------------------------------------------------------------------


def _js_field_render_logic() -> str:
    """Return the JS code for rendering form fields dynamically."""
    return (
        "    var wrapper = document.createElement('div');\n"
        "    wrapper.style.marginBottom = '12px';\n"
        "    var label = document.createElement('label');\n"
        "    label.textContent = field.label;\n"
        "    label.setAttribute('for', 'g8-' + field.name);\n"
        "    wrapper.appendChild(label);\n"
        "\n"
        "    var input;\n"
        "    if (field.type === 'select' && field.options) {\n"
        "      input = document.createElement('select');\n"
        "      var defaultOpt = document.createElement('option');\n"
        "      defaultOpt.value = '';\n"
        "      defaultOpt.textContent = 'Select...';\n"
        "      input.appendChild(defaultOpt);\n"
        "      field.options.forEach(function(opt) {\n"
        "        var o = document.createElement('option');\n"
        "        o.value = opt;\n"
        "        o.textContent = opt;\n"
        "        input.appendChild(o);\n"
        "      });\n"
        "    } else {\n"
        "      input = document.createElement('input');\n"
        "      input.type = field.type || 'text';\n"
        "    }\n"
        "    input.name = field.name;\n"
        "    input.id = 'g8-' + field.name;\n"
        "    if (field.required) input.required = true;\n"
        "    if (savedData[field.name]) input.value = savedData[field.name];\n"
        "    wrapper.appendChild(input);\n"
        "    form.appendChild(wrapper);"
    )


def _js_form_state_init(stages_json: str) -> str:
    """Return JS code for initializing form state from localStorage."""
    return (
        f"  var FIELD_STAGES = {stages_json};\n"
        "\n"
        "  var storageKey = 'g8_form_stage';\n"
        "  var dataKey = 'g8_form_data';\n"
        "  var currentStage = parseInt(localStorage.getItem(storageKey) || '1', 10);\n"
        "  var savedData = JSON.parse(localStorage.getItem(dataKey) || '{}');"
    )


def _js_render_fields_fn() -> str:
    """Return the renderFields function body."""
    field_render = _js_field_render_logic()
    return (
        "  function getStage(n) {\n"
        "    return FIELD_STAGES.find(function(s) { return s.stage === n; });\n"
        "  }\n"
        "\n"
        "  function renderFields() {\n"
        "    var stage = getStage(currentStage);\n"
        "    if (!stage) {\n"
        "      form.style.display = 'none';\n"
        "      thankYou.style.display = 'block';\n"
        "      return;\n"
        "    }\n"
        "    form.innerHTML = '';\n"
        "    stage.fields.forEach(function(field) {\n"
        f"{field_render}\n"
        "    });\n"
        "\n"
        "    var btn = document.createElement('button');\n"
        "    btn.type = 'submit';\n"
        "    btn.textContent = currentStage < FIELD_STAGES.length ? 'Continue' : 'Submit';\n"
        "    form.appendChild(btn);\n"
        "  }"
    )


def _js_submit_handler() -> str:
    """Return the JS form submit event handler."""
    return (
        "  form.addEventListener('submit', function(e) {\n"
        "    e.preventDefault();\n"
        "    var formData = new FormData(form);\n"
        "    var values = {};\n"
        "    formData.forEach(function(v, k) { values[k] = v; });\n"
        "    var merged = Object.assign({}, savedData, values);\n"
        "\n"
        "    // Send to graph8 CDP\n"
        "    if (window.g8) {\n"
        "      g8.identify(merged.email, merged);\n"
        "      g8.track('form_submitted', {\n"
        "        stage: currentStage,\n"
        "        fields: Object.keys(values),\n"
        "        values: values\n"
        "      });\n"
        "    }\n"
        "\n"
        "    currentStage++;\n"
        "    localStorage.setItem(storageKey, String(currentStage));\n"
        "    localStorage.setItem(dataKey, JSON.stringify(merged));\n"
        "    savedData = merged;\n"
        "    renderFields();\n"
        "  });"
    )


# ---------------------------------------------------------------------------
# HTML templates
# ---------------------------------------------------------------------------


def _html_embedded(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return HTML embedded progressive form template."""
    stages_json = json.dumps(stages)
    state_init = _js_form_state_init(stages_json)
    render_fn = _js_render_fields_fn()
    submit_handler = _js_submit_handler()

    return (
        "<!-- graph8 Progressive Form (Embedded) -->\n"
        "<!-- Requires: graph8 tracking snippet (p.js) installed on the page -->\n"
        '<div id="g8-progressive-form">\n'
        '  <form id="g8-form">\n'
        "    <!-- Fields rendered dynamically by JavaScript -->\n"
        "  </form>\n"
        '  <div id="g8-form-thank-you" style="display:none;">\n'
        "    <h3>Thank you!</h3>\n"
        "    <p>We'll be in touch soon.</p>\n"
        "  </div>\n"
        "</div>\n"
        "\n"
        "<script>\n"
        "(function() {\n"
        f"{state_init}\n"
        "\n"
        "  var form = document.getElementById('g8-form');\n"
        "  var thankYou = document.getElementById('g8-form-thank-you');\n"
        "\n"
        f"{render_fn}\n"
        "\n"
        f"{submit_handler}\n"
        "\n"
        "  renderFields();\n"
        "})();\n"
        "</script>"
    )


def _html_popup_overlay() -> str:
    """Return the HTML markup for the popup overlay."""
    return (
        "<!-- graph8 Progressive Form (Popup) -->\n"
        "<!-- Requires: graph8 tracking snippet (p.js) installed on the page -->\n"
        '<div id="g8-form-overlay" style="display:none; position:fixed; top:0; left:0; '
        "width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:9999; "
        'align-items:center; justify-content:center;">\n'
        '  <div style="background:white; padding:32px; border-radius:8px; '
        'max-width:400px; width:90%; position:relative;">\n'
        '    <button id="g8-form-close" style="position:absolute; top:8px; right:12px; '
        'background:none; border:none; font-size:20px; cursor:pointer;">'
        "&times;</button>\n"
        '    <form id="g8-form"><!-- Fields rendered by JS --></form>\n'
        '    <div id="g8-form-thank-you" style="display:none;">\n'
        "      <h3>Thank you!</h3>\n"
        "      <p>We'll be in touch soon.</p>\n"
        "    </div>\n"
        "  </div>\n"
        "</div>"
    )


def _js_popup_trigger_logic() -> str:
    """Return JS code for popup trigger configuration and handlers."""
    return (
        "  // Popup trigger configuration\n"
        "  var TRIGGER = {\n"
        "    timeOnPage: 5000,      // Show after 5 seconds\n"
        "    scrollDepth: 0.5,      // Show at 50% scroll\n"
        "    exitIntent: true,      // Show on exit intent\n"
        "    dismissDuration: 7     // Days before showing again after dismiss\n"
        "  };\n"
        "\n"
        "  var overlay = document.getElementById('g8-form-overlay');\n"
        "  var closeBtn = document.getElementById('g8-form-close');\n"
        "  var shown = false;\n"
        "  var dismissed = localStorage.getItem('g8_form_dismissed');\n"
        "\n"
        "  if (dismissed && Date.now() < parseInt(dismissed, 10)) return;\n"
        "  // Also skip if form already completed\n"
        "  if (currentStage > FIELD_STAGES.length) return;\n"
        "\n"
        "  function showPopup() {\n"
        "    if (shown) return;\n"
        "    shown = true;\n"
        "    overlay.style.display = 'flex';\n"
        "  }\n"
        "\n"
        "  closeBtn.addEventListener('click', function() {\n"
        "    overlay.style.display = 'none';\n"
        "    var expires = Date.now() + (TRIGGER.dismissDuration * 86400000);\n"
        "    localStorage.setItem('g8_form_dismissed', String(expires));\n"
        "  });\n"
        "\n"
        "  // Time trigger\n"
        "  setTimeout(showPopup, TRIGGER.timeOnPage);\n"
        "\n"
        "  // Scroll trigger\n"
        "  window.addEventListener('scroll', function() {\n"
        "    var scrollPct = window.scrollY / (document.body.scrollHeight - window.innerHeight);\n"
        "    if (scrollPct >= TRIGGER.scrollDepth) showPopup();\n"
        "  });\n"
        "\n"
        "  // Exit intent trigger\n"
        "  if (TRIGGER.exitIntent) {\n"
        "    document.addEventListener('mouseout', function(e) {\n"
        "      if (e.clientY < 0) showPopup();\n"
        "    });\n"
        "  }"
    )


def _html_popup(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return HTML popup progressive form template."""
    stages_json = json.dumps(stages)
    overlay_html = _html_popup_overlay()
    state_init = _js_form_state_init(stages_json)
    render_fn = _js_render_fields_fn()
    submit_handler = _js_submit_handler()
    trigger_logic = _js_popup_trigger_logic()

    return (
        f"{overlay_html}\n"
        "\n"
        "<script>\n"
        "(function() {\n"
        f"{state_init}\n"
        "\n"
        "  var form = document.getElementById('g8-form');\n"
        "  var thankYou = document.getElementById('g8-form-thank-you');\n"
        "\n"
        f"{render_fn}\n"
        "\n"
        f"{submit_handler}\n"
        "\n"
        f"{trigger_logic}\n"
        "\n"
        "  renderFields();\n"
        "})();\n"
        "</script>"
    )


# ---------------------------------------------------------------------------
# React templates
# ---------------------------------------------------------------------------


def _react_field_stages_const(stages: list[dict]) -> str:
    """Return a TypeScript const declaration for field stages."""
    stages_json = json.dumps(stages, indent=2)
    return f"const FIELD_STAGES = {stages_json};"


def _react_jsx_field_map(indent: str = "      ") -> str:
    """Return the JSX block that maps fields to form elements."""
    return (
        f"{indent}{{currentStage.fields.map(field => (\n"
        f"{indent}  <div key={{field.name}} style={{{{ marginBottom: 12 }}}}>\n"
        f"{indent}    <label htmlFor={{field.name}}>{{field.label}}</label>\n"
        f"{indent}    {{field.type === 'select' && field.options ? (\n"
        f"{indent}      <select name={{field.name}} id={{field.name}}\n"
        f"{indent}              required={{field.required}}\n"
        f"{indent}              defaultValue={{formData[field.name] || ''}}>\n"
        f'{indent}        <option value="">Select...</option>\n'
        f"{indent}        {{field.options.map(opt => (\n"
        f"{indent}          <option key={{opt}} value={{opt}}>{{opt}}</option>\n"
        f"{indent}        ))}}\n"
        f"{indent}      </select>\n"
        f"{indent}    ) : (\n"
        f"{indent}      <input type={{field.type || 'text'}} name={{field.name}}\n"
        f"{indent}             id={{field.name}} required={{field.required}}\n"
        f"{indent}             defaultValue={{formData[field.name] || ''}} />\n"
        f"{indent}    )}}\n"
        f"{indent}  </div>\n"
        f"{indent}))}}"
    )


def _react_submit_handler_code() -> str:
    """Return the React handleSubmit function body."""
    return (
        "  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n"
        "    e.preventDefault();\n"
        "    const data = new FormData(e.currentTarget);\n"
        "    const values = Object.fromEntries(data.entries()) as Record<string, string>;\n"
        "    const merged = { ...formData, ...values };\n"
        "    const g8 = (window as any).g8;\n"
        "    if (g8) {\n"
        "      g8.identify(merged.email, merged);\n"
        "      g8.track('form_submitted', { stage, fields: Object.keys(values), ...values });\n"
        "    }\n"
        "    const nextStage = stage + 1;\n"
        "    localStorage.setItem('g8_form_stage', String(nextStage));\n"
        "    localStorage.setItem('g8_form_data', JSON.stringify(merged));\n"
        "    if (nextStage > FIELD_STAGES.length) {\n"
        "      setSubmitted(true);\n"
        "      onComplete?.(merged);\n"
        "    } else {\n"
        "      setStage(nextStage);\n"
        "      setFormData(merged);\n"
        "    }\n"
        "  };"
    )


def _react_component_header(stages: list[dict]) -> str:
    """Return React component imports, constants, and state setup."""
    stages_const = _react_field_stages_const(stages)
    return (
        "// G8ProgressiveForm.tsx\n"
        "'use client';\n"
        "import { useState, useEffect, FormEvent } from 'react';\n"
        "\n"
        f"{stages_const}\n"
        "\n"
        "export function G8ProgressiveForm("
        "{ onComplete }: { onComplete?: (data: Record<string, string>) => void }"
        ") {\n"
        "  const [stage, setStage] = useState(1);\n"
        "  const [formData, setFormData] = useState<Record<string, string>>({});\n"
        "  const [submitted, setSubmitted] = useState(false);\n"
        "\n"
        "  useEffect(() => {\n"
        "    const saved = localStorage.getItem('g8_form_stage');\n"
        "    if (saved) setStage(parseInt(saved, 10));\n"
        "    const data = localStorage.getItem('g8_form_data');\n"
        "    if (data) setFormData(JSON.parse(data));\n"
        "  }, []);\n"
        "\n"
        "  const currentStage = FIELD_STAGES.find(s => s.stage === stage);\n"
        "\n"
        "  if (!currentStage || submitted) {\n"
        "    return <div><h3>Thank you!</h3><p>We'll be in touch soon.</p></div>;\n"
        "  }"
    )


def _react_embedded_component(stages: list[dict]) -> str:
    """Return the core React form component code."""
    header = _react_component_header(stages)
    submit_handler = _react_submit_handler_code()
    field_map = _react_jsx_field_map()
    return (
        f"{header}\n\n"
        f"{submit_handler}\n\n"
        "  return (\n"
        "    <form onSubmit={handleSubmit}>\n"
        f"{field_map}\n"
        "      <button type=\"submit\">\n"
        "        {stage < FIELD_STAGES.length ? 'Continue' : 'Submit'}\n"
        "      </button>\n"
        "    </form>\n"
        "  );\n"
        "}"
    )


def _react_embedded(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return React embedded progressive form template."""
    return _react_embedded_component(stages)


def _react_popup_header(stages: list[dict]) -> str:
    """Return React popup imports, trigger config, and constants."""
    stages_const = _react_field_stages_const(stages)
    return (
        "// G8ProgressiveFormPopup.tsx\n"
        "'use client';\n"
        "import { useState, useEffect, useCallback, FormEvent } from 'react';\n"
        "\n"
        f"{stages_const}\n"
        "\n"
        "const TRIGGER = {\n"
        "  timeOnPage: 5000,\n"
        "  scrollDepth: 0.5,\n"
        "  exitIntent: true,\n"
        "  dismissDuration: 7,\n"
        "};"
    )


def _react_popup_hooks() -> str:
    """Return React popup state hooks, trigger effects, and dismiss."""
    return (
        "  const [visible, setVisible] = useState(false);\n"
        "  const [stage, setStage] = useState(1);\n"
        "  const [formData, setFormData] = useState<Record<string, string>>({});\n"
        "  const [submitted, setSubmitted] = useState(false);\n"
        "\n"
        "  useEffect(() => {\n"
        "    const saved = localStorage.getItem('g8_form_stage');\n"
        "    if (saved) setStage(parseInt(saved, 10));\n"
        "    const data = localStorage.getItem('g8_form_data');\n"
        "    if (data) setFormData(JSON.parse(data));\n"
        "  }, []);\n"
        "\n"
        "  const showPopup = useCallback(() => {\n"
        "    const dismissed = localStorage.getItem('g8_form_dismissed');\n"
        "    if (dismissed && Date.now() < parseInt(dismissed, 10)) return;\n"
        "    setVisible(true);\n"
        "  }, []);"
    )


def _react_popup_trigger_effect() -> str:
    """Return the React useEffect for popup triggers."""
    return (
        "  useEffect(() => {\n"
        "    const timer = setTimeout(showPopup, TRIGGER.timeOnPage);\n"
        "    const onScroll = () => {\n"
        "      const pct = window.scrollY / (document.body.scrollHeight - window.innerHeight);\n"
        "      if (pct >= TRIGGER.scrollDepth) showPopup();\n"
        "    };\n"
        "    const onMouseOut = (e: MouseEvent) => {\n"
        "      if (e.clientY < 0) showPopup();\n"
        "    };\n"
        "    window.addEventListener('scroll', onScroll);\n"
        "    if (TRIGGER.exitIntent) document.addEventListener('mouseout', onMouseOut);\n"
        "    return () => {\n"
        "      clearTimeout(timer);\n"
        "      window.removeEventListener('scroll', onScroll);\n"
        "      document.removeEventListener('mouseout', onMouseOut);\n"
        "    };\n"
        "  }, [showPopup]);\n"
        "\n"
        "  const handleDismiss = () => {\n"
        "    setVisible(false);\n"
        "    const expires = Date.now() + (TRIGGER.dismissDuration * 86400000);\n"
        "    localStorage.setItem('g8_form_dismissed', String(expires));\n"
        "  };\n"
        "\n"
        "  const currentStage = FIELD_STAGES.find(s => s.stage === stage);"
    )


def _react_popup_jsx() -> str:
    """Return the React popup overlay JSX."""
    field_map = _react_jsx_field_map(indent="            ")
    return (
        "  if (!visible) return null;\n"
        "\n"
        "  return (\n"
        "    <div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',\n"
        "                  background: 'rgba(0,0,0,0.5)', zIndex: 9999,\n"
        "                  display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\n"
        "      <div style={{ background: 'white', padding: 32, borderRadius: 8,\n"
        "                    maxWidth: 400, width: '90%', position: 'relative' }}>\n"
        "        <button onClick={handleDismiss}\n"
        "                style={{ position: 'absolute', top: 8, right: 12,\n"
        "                         background: 'none', border: 'none',\n"
        "                         fontSize: 20, cursor: 'pointer' }}>\n"
        "          &times;\n"
        "        </button>\n"
        "        {(!currentStage || submitted) ? (\n"
        "          <div><h3>Thank you!</h3><p>We'll be in touch soon.</p></div>\n"
        "        ) : (\n"
        "          <form onSubmit={handleSubmit}>\n"
        f"{field_map}\n"
        "            <button type=\"submit\">\n"
        "              {stage < FIELD_STAGES.length ? 'Continue' : 'Submit'}\n"
        "            </button>\n"
        "          </form>\n"
        "        )}\n"
        "      </div>\n"
        "    </div>\n"
        "  );\n"
        "}"
    )


def _react_popup_wrapper(stages: list[dict]) -> str:
    """Return the React popup form wrapper component."""
    header = _react_popup_header(stages)
    hooks = _react_popup_hooks()
    trigger = _react_popup_trigger_effect()
    submit = _react_submit_handler_code()
    jsx = _react_popup_jsx()
    return (
        f"{header}\n\n"
        "export function G8ProgressiveFormPopup("
        "{ onComplete }: { onComplete?: (data: Record<string, string>) => void }"
        ") {\n"
        f"{hooks}\n\n"
        f"{trigger}\n\n"
        f"{submit}\n\n"
        f"{jsx}"
    )


def _react_popup(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return React popup progressive form template."""
    return _react_popup_wrapper(stages)


# ---------------------------------------------------------------------------
# Next.js templates (reuses React with a note)
# ---------------------------------------------------------------------------

_NEXTJS_NOTE = (
    "// NOTE: This component works in both App Router and Pages Router.\n"
    "// Make sure the graph8 tracking snippet is installed in your layout.\n"
    "// Use g8_get_tracking_snippet with framework='nextjs' for setup.\n\n"
)


def _nextjs_embedded(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return Next.js embedded form (React component with note)."""
    return _NEXTJS_NOTE + _react_embedded_component(stages)


def _nextjs_popup(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return Next.js popup form (React popup with note)."""
    return _NEXTJS_NOTE + _react_popup_wrapper(stages)


# ---------------------------------------------------------------------------
# Vue templates
# ---------------------------------------------------------------------------


def _vue_embedded_template(stages: list[dict]) -> str:
    """Return the Vue SFC template block for embedded form."""
    return (
        "<template>\n"
        '  <div v-if="!completed">\n'
        '    <form @submit.prevent="handleSubmit">\n'
        '      <div v-for="field in currentFields" :key="field.name"'
        ' style="margin-bottom: 12px">\n'
        "        <label :for=\"field.name\">{{ field.label }}</label>\n"
        '        <select v-if="field.type === \'select\'" :name="field.name"\n'
        '                :id="field.name" :required="field.required"\n'
        '                v-model="values[field.name]">\n'
        '          <option value="">Select...</option>\n'
        '          <option v-for="opt in field.options" :key="opt"\n'
        '                  :value="opt">{{ opt }}</option>\n'
        "        </select>\n"
        '        <input v-else :type="field.type || \'text\'" :name="field.name"\n'
        '               :id="field.name" :required="field.required"\n'
        '               v-model="values[field.name]" />\n'
        "      </div>\n"
        '      <button type="submit">\n'
        "        {{ stage < totalStages ? 'Continue' : 'Submit' }}\n"
        "      </button>\n"
        "    </form>\n"
        "  </div>\n"
        "  <div v-else>\n"
        "    <h3>Thank you!</h3>\n"
        "    <p>We'll be in touch soon.</p>\n"
        "  </div>\n"
        "</template>"
    )


def _vue_script_block(stages: list[dict]) -> str:
    """Return the Vue SFC script setup block."""
    stages_json = json.dumps(stages, indent=2)
    return (
        "<script setup>\n"
        "import { ref, computed, onMounted } from 'vue';\n"
        "\n"
        f"const FIELD_STAGES = {stages_json};\n"
        "\n"
        "const stage = ref(1);\n"
        "const values = ref({});\n"
        "const completed = ref(false);\n"
        "const totalStages = FIELD_STAGES.length;\n"
        "\n"
        "onMounted(() => {\n"
        "  const saved = localStorage.getItem('g8_form_stage');\n"
        "  if (saved) stage.value = parseInt(saved, 10);\n"
        "  const data = localStorage.getItem('g8_form_data');\n"
        "  if (data) values.value = JSON.parse(data);\n"
        "  if (stage.value > totalStages) completed.value = true;\n"
        "});\n"
        "\n"
        "const currentFields = computed(() => {\n"
        "  const s = FIELD_STAGES.find(s => s.stage === stage.value);\n"
        "  return s ? s.fields : [];\n"
        "});\n"
        "\n"
        "function handleSubmit() {\n"
        "  const merged = { ...values.value };\n"
        "  const g8 = window.g8;\n"
        "  if (g8) {\n"
        "    g8.identify(merged.email, merged);\n"
        "    g8.track('form_submitted', {\n"
        "      stage: stage.value,\n"
        "      fields: Object.keys(merged),\n"
        "      values: merged\n"
        "    });\n"
        "  }\n"
        "  const next = stage.value + 1;\n"
        "  localStorage.setItem('g8_form_stage', String(next));\n"
        "  localStorage.setItem('g8_form_data', JSON.stringify(merged));\n"
        "  if (next > totalStages) {\n"
        "    completed.value = true;\n"
        "  } else {\n"
        "    stage.value = next;\n"
        "  }\n"
        "}\n"
        "</script>"
    )


def _vue_embedded(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return Vue embedded progressive form template."""
    template = _vue_embedded_template(stages)
    script = _vue_script_block(stages)
    return (
        "<!-- G8ProgressiveForm.vue -->\n"
        "<!-- Requires: graph8 tracking snippet (p.js) installed -->\n\n"
        f"{template}\n\n"
        f"{script}"
    )


def _vue_popup_template_block() -> str:
    """Return the Vue SFC template block for popup form."""
    return (
        "<template>\n"
        '  <Teleport to="body">\n'
        '    <div v-if="visible" style="position:fixed; top:0; left:0; width:100%;\n'
        '         height:100%; background:rgba(0,0,0,0.5); z-index:9999;\n'
        '         display:flex; align-items:center; justify-content:center;">\n'
        '      <div style="background:white; padding:32px; border-radius:8px;\n'
        '           max-width:400px; width:90%; position:relative;">\n'
        '        <button @click="dismiss" style="position:absolute; top:8px; right:12px;\n'
        '                background:none; border:none; font-size:20px;\n'
        '                cursor:pointer;">&times;</button>\n'
        '        <div v-if="completed">\n'
        "          <h3>Thank you!</h3>\n"
        "          <p>We'll be in touch soon.</p>\n"
        "        </div>\n"
        '        <form v-else @submit.prevent="handleSubmit">\n'
        '          <div v-for="field in currentFields" :key="field.name"\n'
        '               style="margin-bottom: 12px">\n'
        "            <label :for=\"field.name\">{{ field.label }}</label>\n"
        '            <select v-if="field.type === \'select\'" :name="field.name"\n'
        '                    :id="field.name" :required="field.required"\n'
        '                    v-model="values[field.name]">\n'
        '              <option value="">Select...</option>\n'
        '              <option v-for="opt in field.options" :key="opt"\n'
        '                      :value="opt">{{ opt }}</option>\n'
        "            </select>\n"
        '            <input v-else :type="field.type || \'text\'" :name="field.name"\n'
        '                   :id="field.name" :required="field.required"\n'
        '                   v-model="values[field.name]" />\n'
        "          </div>\n"
        '          <button type="submit">\n'
        "            {{ stage < totalStages ? 'Continue' : 'Submit' }}\n"
        "          </button>\n"
        "        </form>\n"
        "      </div>\n"
        "    </div>\n"
        "  </Teleport>\n"
        "</template>"
    )


def _vue_popup_script_setup(stages: list[dict]) -> str:
    """Return Vue popup script setup: imports, state, and trigger config."""
    stages_json = json.dumps(stages, indent=2)
    return (
        "<script setup>\n"
        "import { ref, computed, onMounted, onUnmounted } from 'vue';\n\n"
        f"const FIELD_STAGES = {stages_json};\n\n"
        "const TRIGGER = {\n"
        "  timeOnPage: 5000,\n"
        "  scrollDepth: 0.5,\n"
        "  exitIntent: true,\n"
        "  dismissDuration: 7,\n"
        "};\n\n"
        "const stage = ref(1);\n"
        "const values = ref({});\n"
        "const completed = ref(false);\n"
        "const visible = ref(false);\n"
        "const totalStages = FIELD_STAGES.length;\n"
        "let timer = null;"
    )


def _vue_popup_lifecycle_fns() -> str:
    """Return Vue popup lifecycle hooks and trigger functions."""
    return (
        "onMounted(() => {\n"
        "  const saved = localStorage.getItem('g8_form_stage');\n"
        "  if (saved) stage.value = parseInt(saved, 10);\n"
        "  const data = localStorage.getItem('g8_form_data');\n"
        "  if (data) values.value = JSON.parse(data);\n"
        "  if (stage.value > totalStages) { completed.value = true; return; }\n"
        "  const dismissed = localStorage.getItem('g8_form_dismissed');\n"
        "  if (dismissed && Date.now() < parseInt(dismissed, 10)) return;\n"
        "  timer = setTimeout(showPopup, TRIGGER.timeOnPage);\n"
        "  window.addEventListener('scroll', onScroll);\n"
        "  if (TRIGGER.exitIntent) document.addEventListener('mouseout', onMouseOut);\n"
        "});\n\n"
        "onUnmounted(() => {\n"
        "  if (timer) clearTimeout(timer);\n"
        "  window.removeEventListener('scroll', onScroll);\n"
        "  document.removeEventListener('mouseout', onMouseOut);\n"
        "});\n\n"
        "function showPopup() { visible.value = true; }\n"
        "function onScroll() {\n"
        "  const pct = window.scrollY / (document.body.scrollHeight - window.innerHeight);\n"
        "  if (pct >= TRIGGER.scrollDepth) showPopup();\n"
        "}\n"
        "function onMouseOut(e) { if (e.clientY < 0) showPopup(); }\n\n"
        "function dismiss() {\n"
        "  visible.value = false;\n"
        "  const expires = Date.now() + (TRIGGER.dismissDuration * 86400000);\n"
        "  localStorage.setItem('g8_form_dismissed', String(expires));\n"
        "}"
    )


def _vue_popup_submit_fn() -> str:
    """Return Vue popup computed fields and handleSubmit function."""
    return (
        "const currentFields = computed(() => {\n"
        "  const s = FIELD_STAGES.find(s => s.stage === stage.value);\n"
        "  return s ? s.fields : [];\n"
        "});\n\n"
        "function handleSubmit() {\n"
        "  const merged = { ...values.value };\n"
        "  const g8 = window.g8;\n"
        "  if (g8) {\n"
        "    g8.identify(merged.email, merged);\n"
        "    g8.track('form_submitted', {\n"
        "      stage: stage.value, fields: Object.keys(merged), values: merged\n"
        "    });\n"
        "  }\n"
        "  const next = stage.value + 1;\n"
        "  localStorage.setItem('g8_form_stage', String(next));\n"
        "  localStorage.setItem('g8_form_data', JSON.stringify(merged));\n"
        "  if (next > totalStages) { completed.value = true; }\n"
        "  else { stage.value = next; }\n"
        "}\n"
        "</script>"
    )


def _vue_popup_script_block(stages: list[dict]) -> str:
    """Return the complete Vue SFC script setup block for popup form."""
    setup = _vue_popup_script_setup(stages)
    lifecycle = _vue_popup_lifecycle_fns()
    submit = _vue_popup_submit_fn()
    return f"{setup}\n\n{lifecycle}\n\n{submit}"


def _vue_popup(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return Vue popup progressive form template."""
    template = _vue_popup_template_block()
    script = _vue_popup_script_block(stages)
    return (
        "<!-- G8ProgressiveFormPopup.vue -->\n"
        "<!-- Requires: graph8 tracking snippet (p.js) installed -->\n\n"
        f"{template}\n\n"
        f"{script}"
    )


# ---------------------------------------------------------------------------
# WordPress template
# ---------------------------------------------------------------------------


def _wordpress_embedded(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return WordPress shortcode for embedded form."""
    html_form = _html_embedded(write_key, tracking_host, stages)
    # Escape PHP-sensitive characters in the HTML template
    escaped_form = html_form.replace("<?", "< ?").replace("?>", "? >")
    return (
        "// Add to your theme's functions.php:\n"
        "function g8_progressive_form_shortcode($atts) {\n"
        "    ob_start();\n"
        "    ?>\n"
        f"    {escaped_form}\n"
        "    <?php\n"
        "    return ob_get_clean();\n"
        "}\n"
        "add_shortcode('g8_form', 'g8_progressive_form_shortcode');\n"
        "// Usage: [g8_form]"
    )


def _wordpress_popup(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return WordPress shortcode for popup form."""
    html_form = _html_popup(write_key, tracking_host, stages)
    escaped_form = html_form.replace("<?", "< ?").replace("?>", "? >")
    return (
        "// Add to your theme's functions.php:\n"
        "function g8_progressive_form_popup_shortcode($atts) {\n"
        "    ob_start();\n"
        "    ?>\n"
        f"    {escaped_form}\n"
        "    <?php\n"
        "    return ob_get_clean();\n"
        "}\n"
        "add_shortcode('g8_form_popup', 'g8_progressive_form_popup_shortcode');\n"
        "// Usage: [g8_form_popup]"
    )


# ---------------------------------------------------------------------------
# Webflow template (HTML with instructions)
# ---------------------------------------------------------------------------

_WEBFLOW_INSTRUCTIONS = (
    "// Webflow Installation:\n"
    "// 1. Add an Embed element where you want the form to appear\n"
    "// 2. Paste the code below into the Embed element\n"
    "// 3. For site-wide popup forms, go to Project Settings -> Custom Code\n"
    "//    and paste in the Footer Code section\n"
    "// 4. Publish the site to apply changes\n\n"
)


def _webflow_embedded(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return Webflow embedded form (HTML with instructions)."""
    return _WEBFLOW_INSTRUCTIONS + _html_embedded(write_key, tracking_host, stages)


def _webflow_popup(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return Webflow popup form (HTML with instructions)."""
    return _WEBFLOW_INSTRUCTIONS + _html_popup(write_key, tracking_host, stages)


# ---------------------------------------------------------------------------
# Shopify template (Liquid section wrapper)
# ---------------------------------------------------------------------------


def _shopify_section_wrapper(inner_html: str) -> str:
    """Wrap HTML form code in a Shopify Liquid section."""
    return (
        "<!-- sections/g8-form.liquid -->\n"
        f"{inner_html}\n"
        "\n"
        "{% schema %}\n"
        "{\n"
        '  "name": "graph8 Form",\n'
        '  "settings": []\n'
        "}\n"
        "{% endschema %}"
    )


def _shopify_embedded(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return Shopify embedded form (Liquid section)."""
    html_form = _html_embedded(write_key, tracking_host, stages)
    return _shopify_section_wrapper(html_form)


def _shopify_popup(write_key: str, tracking_host: str, stages: list[dict]) -> str:
    """Return Shopify popup form (Liquid section)."""
    html_form = _html_popup(write_key, tracking_host, stages)
    return _shopify_section_wrapper(html_form)


# ---------------------------------------------------------------------------
# Template registry
# ---------------------------------------------------------------------------

_TEMPLATE_REGISTRY: dict[tuple[str, str], callable] = {
    ("html", "embedded"): _html_embedded,
    ("html", "popup"): _html_popup,
    ("react", "embedded"): _react_embedded,
    ("react", "popup"): _react_popup,
    ("nextjs", "embedded"): _nextjs_embedded,
    ("nextjs", "popup"): _nextjs_popup,
    ("vue", "embedded"): _vue_embedded,
    ("vue", "popup"): _vue_popup,
    ("wordpress", "embedded"): _wordpress_embedded,
    ("wordpress", "popup"): _wordpress_popup,
    ("webflow", "embedded"): _webflow_embedded,
    ("webflow", "popup"): _webflow_popup,
    ("shopify", "embedded"): _shopify_embedded,
    ("shopify", "popup"): _shopify_popup,
}


def build_form_result(
    framework: str,
    variant: str,
    write_key: str,
    tracking_host: str,
    field_stages: list[dict] | None = None,
) -> dict[str, Any]:
    """Build form template result for CLI use — no API call needed."""
    stages = field_stages or DEFAULT_FIELD_STAGES
    template_fn = _TEMPLATE_REGISTRY[(framework, variant)]
    template_code = template_fn(write_key, tracking_host, stages)
    return _build_result(write_key, tracking_host, framework, variant, template_code, stages)
