"""
Email Rendering Pipeline

Converts Puck email templates to:
- Email-safe HTML (table-based layout, inline styles)
- Plain text fallback
- Subject with tag substitution

All output is sanitized and HTML-escaped for security.
"""
import re
from html import escape as html_escape
from typing import Dict, Any, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from .models import PuckEmailTemplate

# =============================================================================
# Tag Substitution
# =============================================================================

TAG_PATTERN = re.compile(r'\{\{\s*(\w+)\s*\}\}')


def substitute_tags(text: str, context: Dict[str, Any], escape_html: bool = True) -> str:
    """
    Replace {{ tag_name }} with context values.

    Args:
        text: Template text with {{ tag }} placeholders
        context: Dictionary of tag values
        escape_html: Whether to HTML-escape substituted values

    Returns:
        Text with tags replaced
    """
    if not text:
        return ''

    def replace_tag(match):
        tag_name = match.group(1)
        value = context.get(tag_name, match.group(0))  # Keep original if not found
        str_value = str(value) if value is not None else ''
        if escape_html:
            return html_escape(str_value)
        return str_value

    return TAG_PATTERN.sub(replace_tag, text)


def render_subject(subject_template: str, context: Dict[str, Any]) -> str:
    """
    Render email subject line.

    Args:
        subject_template: Subject with {{ tag }} placeholders
        context: Tag values

    Returns:
        Rendered subject (HTML-escaped)
    """
    return substitute_tags(subject_template, context, escape_html=True)


# =============================================================================
# HTML Email Rendering
# =============================================================================

# Email-safe CSS (inlined into elements)
EMAIL_STYLES = {
    'wrapper': 'width: 100%; background-color: #f4f4f5; padding: 40px 20px; margin: 0;',
    'container': 'max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden;',
    'header': 'padding: 32px 40px; text-align: center; background-color: #f8fafc;',
    'body': 'padding: 40px;',
    'footer': 'padding: 24px 40px; background-color: #f8fafc; text-align: center; font-size: 13px; color: #6b7280;',
    'heading_h1': 'font-size: 28px; font-weight: 700; color: #111827; margin: 0 0 16px 0; line-height: 1.3;',
    'heading_h2': 'font-size: 22px; font-weight: 600; color: #111827; margin: 0 0 12px 0; line-height: 1.3;',
    'heading_h3': 'font-size: 18px; font-weight: 600; color: #374151; margin: 0 0 8px 0; line-height: 1.3;',
    'text': 'font-size: 16px; line-height: 1.6; color: #374151; margin: 0 0 16px 0;',
    'button': 'display: inline-block; padding: 14px 28px; background-color: #4f46e5; color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 16px;',
    'button_secondary': 'display: inline-block; padding: 12px 24px; background-color: #ffffff; color: #4f46e5; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 14px; border: 2px solid #4f46e5;',
    'divider': 'border: 0; border-top: 1px solid #e5e7eb; margin: 24px 0;',
    'spacer_sm': 'height: 16px;',
    'spacer_md': 'height: 32px;',
    'spacer_lg': 'height: 48px;',
    'panel': 'padding: 20px; background-color: #f3f4f6; border-radius: 6px; margin: 16px 0;',
    'image': 'max-width: 100%; height: auto; display: block;',
    'link': 'color: #4f46e5; text-decoration: underline;',
}


def render_component_html(component: Dict[str, Any], context: Dict[str, Any]) -> str:
    """
    Render a single Puck component to email-safe HTML.

    Args:
        component: Puck component dict with 'type' and 'props'
        context: Tag substitution context

    Returns:
        HTML string
    """
    comp_type = component.get('type', '')
    props = component.get('props', {})

    # Substitute tags in all string props
    for key, value in props.items():
        if isinstance(value, str):
            props[key] = substitute_tags(value, context, escape_html=True)

    # Render based on component type
    renderers = {
        'EmailLayout': render_email_layout,
        'EmailHeader': render_email_header,
        'EmailHeading': render_email_heading,
        'EmailText': render_email_text,
        'EmailButton': render_email_button,
        'EmailDivider': render_email_divider,
        'EmailSpacer': render_email_spacer,
        'EmailImage': render_email_image,
        'EmailPanel': render_email_panel,
        'EmailTwoColumn': render_email_two_column,
        'EmailFooter': render_email_footer,
        'EmailBranding': render_email_branding,
    }

    renderer = renderers.get(comp_type, render_unknown_component)
    return renderer(props, context)


def render_email_layout(props: dict, context: dict) -> str:
    """Render root email layout wrapper."""
    bg_color = props.get('backgroundColor', '#f4f4f5')
    content_bg = props.get('contentBackgroundColor', '#ffffff')

    return f'''<div style="width: 100%; background-color: {bg_color}; padding: 40px 20px; margin: 0;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="max-width: 600px; margin: 0 auto; background-color: {content_bg}; border-radius: 8px;">
<tr><td style="padding: 0;">'''


def render_email_header(props: dict, context: dict) -> str:
    """Render email header with optional logo."""
    logo_url = props.get('logoUrl', '')
    business_name = props.get('businessName', context.get('business_name', ''))
    preheader = props.get('preheader', '')

    html = f'<div style="{EMAIL_STYLES["header"]}">'

    if logo_url:
        html += f'<img src="{logo_url}" alt="{business_name}" style="max-height: 60px; max-width: 200px; margin-bottom: 16px;" />'

    if business_name:
        html += f'<div style="font-size: 20px; font-weight: 600; color: #111827;">{business_name}</div>'

    html += '</div>'

    # Hidden preheader text
    if preheader:
        html = f'<div style="display: none; max-height: 0; overflow: hidden;">{preheader}</div>' + html

    return html


def render_email_heading(props: dict, context: dict) -> str:
    """Render heading (h1-h3)."""
    text = props.get('text', '')
    level = props.get('level', 'h2')
    align = props.get('align', 'left')

    style_key = f'heading_{level}'
    style = EMAIL_STYLES.get(style_key, EMAIL_STYLES['heading_h2'])
    style += f' text-align: {align};'

    return f'<{level} style="{style}">{text}</{level}>'


def render_email_text(props: dict, context: dict) -> str:
    """Render text/paragraph content."""
    content = props.get('content', '')
    align = props.get('align', 'left')

    style = EMAIL_STYLES['text'] + f' text-align: {align};'

    # Convert newlines to <br>
    content = content.replace('\n', '<br>')

    return f'<p style="{style}">{content}</p>'


def render_email_button(props: dict, context: dict) -> str:
    """Render CTA button (table-based for email clients)."""
    text = props.get('text', 'Click Here')
    href = props.get('href', '#')
    variant = props.get('variant', 'primary')
    align = props.get('align', 'center')

    style = EMAIL_STYLES['button'] if variant == 'primary' else EMAIL_STYLES['button_secondary']

    # Table-based button for maximum email client compatibility
    return f'''<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin: 16px 0; width: 100%;">
<tr><td align="{align}">
<a href="{href}" target="_blank" style="{style}">{text}</a>
</td></tr></table>'''


def render_email_divider(props: dict, context: dict) -> str:
    """Render horizontal divider."""
    return f'<hr style="{EMAIL_STYLES["divider"]}" />'


def render_email_spacer(props: dict, context: dict) -> str:
    """Render vertical spacer."""
    size = props.get('size', 'md')
    style_key = f'spacer_{size}'
    style = EMAIL_STYLES.get(style_key, EMAIL_STYLES['spacer_md'])
    return f'<div style="{style}"></div>'


def render_email_image(props: dict, context: dict) -> str:
    """Render image."""
    src = props.get('src', '')
    alt = props.get('alt', '')
    max_width = props.get('maxWidth', '100%')
    align = props.get('align', 'center')

    if not src:
        return ''

    style = EMAIL_STYLES['image'] + f' max-width: {max_width};'

    return f'''<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width: 100%; margin: 16px 0;">
<tr><td align="{align}">
<img src="{src}" alt="{alt}" style="{style}" />
</td></tr></table>'''


def render_email_panel(props: dict, context: dict) -> str:
    """Render highlighted panel/info box."""
    content = props.get('content', '')
    bg_color = props.get('backgroundColor', '#f3f4f6')

    style = EMAIL_STYLES['panel'].replace('#f3f4f6', bg_color)

    return f'<div style="{style}">{content}</div>'


def render_email_two_column(props: dict, context: dict) -> str:
    """Render two-column layout (table-based)."""
    left_content = props.get('leftContent', '')
    right_content = props.get('rightContent', '')
    gap = props.get('gap', '20px')

    return f'''<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width: 100%;">
<tr>
<td style="width: 50%; vertical-align: top; padding-right: {gap};">{left_content}</td>
<td style="width: 50%; vertical-align: top; padding-left: {gap};">{right_content}</td>
</tr></table>'''


def render_email_footer(props: dict, context: dict) -> str:
    """Render email footer."""
    address = props.get('address', context.get('business_address', ''))
    phone = props.get('phone', context.get('business_phone', ''))
    email = props.get('email', context.get('business_email', ''))
    website = props.get('website', context.get('business_website_url', ''))

    html = f'<div style="{EMAIL_STYLES["footer"]}">'

    if address:
        html += f'<p style="margin: 0 0 8px 0;">{address}</p>'

    contact_parts = []
    if phone:
        contact_parts.append(phone)
    if email:
        contact_parts.append(f'<a href="mailto:{email}" style="{EMAIL_STYLES["link"]}">{email}</a>')
    if website:
        contact_parts.append(f'<a href="{website}" style="{EMAIL_STYLES["link"]}">{website}</a>')

    if contact_parts:
        html += f'<p style="margin: 0 0 8px 0;">{" | ".join(contact_parts)}</p>'

    html += '</div>'
    return html


def render_email_branding(props: dict, context: dict) -> str:
    """
    Render SmoothSchedule branding footer.

    This is shown for free plans without white-label permission.
    The show_branding flag is controlled by the rendering context
    based on the tenant's billing plan and white-label permission.
    """
    # Check if branding should be shown (from props or context)
    show_branding = props.get('showBranding', True)

    # Context can override - white-label permission hides branding
    if context.get('can_remove_branding', False):
        show_branding = False

    if not show_branding:
        return ''

    # SmoothSchedule logo as embedded data URL (works everywhere without external hosting)
    logo_data_url = ('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAj4UlEQVR42u19e5xcZX339/d7' +
                     'nnNmNhdCEFaoCAgpkNkEKBGpQJkoirbSitAB9r4hEgXFKrVYL3UY+hY/FdH6QlEXgew1wPhiVWyjRc2UUqwaBZKdBAhCKi' +
                     'U2xMRcd+ac53l+7x/nnNnZzeYGudr5fj75EHazs+ec7/O7Xw7QQAMNNNBAAw000EADDTTQQAMNNNBAAw000EADDTTQQAMN' +
                     'NNBAAw000EADDTTQQAMNNNAAQP8r7zmfJwDIlcu1+y8CQCYjAIBCQQBI43gcCcjnOZvN6mw2q/fpQMc/l8vlFESoIcGHG6' +
                     'nLlnGpVDITvzWrvf0oT1IzlTYznKijGFAAYAUVsN3mA7/dPmPGhjV33lmd+Jm5cpmKxaI7kqSbfteIRblMKBZt8qWWzoWz' +
                     'WeR8B/kjQOYI5GQSHA2iFDGDQAAB4hzECQSyg4BXwPw8iSwnwuOhC3+yemhoXfKZuVxO7ZHofJ5RKLgGwfuL2Mh2OgCY3X' +
                     'btycTuGia6UkTOVZ6ngIREBxEBoj9jBBARAAIRiBikGEQMEQdnzGYilEA0lNroPbL8kd4deyCaDhcpP9IJplwux8VYYmd3' +
                     'LDhfkfwFgD9l7U8TZ+GMgQAWgESyCt7DvQtEBEQiEAGImEiR1iAQnDVrRORr7IJ7VwwPb4pFWtW0Rvz3lrauOySk28vFvl' +
                     '8fZMLH/S46oqU2ltiWjo6zifRnALqStSYbhhBxhgRck8yYOCFy0f0nyjlhNfmPROIsEoly/HUBHACwUoqVhjPmV4B8OVX2' +
                     '/u/y5b1hLpdT69dnqFQqmExr503+1Gl3VHbsePPq4b7l4w5AQ4L3jGw2q0ulkjmro2OqJe9vCPgoa52yQSACOIqklOqJIS' +
                     'LFrECKASJAIkGdqKEjNmNVbi0EkhwUTr4pgGOlNGsPzpifWxt+YtXwwKOReeh8h1L6+6y12Er1ppEl/V9OrvdgSO5ZHR3N' +
                     'BjiqPDi4BgD0EaqSzRnXdL7Zkb5fed4cW63CBIElQFHkFYtADBFrpbUiItggrDhrnoPDShGsBsmvIPgNJXZYeJooORaOTy' +
                     'OS0wHKEPMpyvO0WAsXhk6IhIgUASzWirHWKs87V7P/ry1tXX8Liz4QD8T2noVwOYAvl+bPdyiVDvShV6VSyRjHH3dkv3Qk' +
                     'SnCUoCgUXKaj68NM+nZiStswNESkahIr4lgpxdqDDYNRgH4E4GEF9cOnB7/+wt7+spO7u9PTnZojkHdBcAUrPpeIYcOall' +
                     'A1cSdi1houDLeSUtPFWkfMLOK2KXKnPT04uB6Ryj8gdnjeokXe8t7esKWj+w5xUOXhvo9m83ldKhTMESLBsX9UKLhMR/c/' +
                     'aj91gw0CuNA5ItKx8bREpHQqpVwQ/tqZ8OsC1VcevHdN/SclthJYhubm5toDX79+fe2wl+bPd2sLhQqAn8V/bpvT0fMOB/' +
                     'chYn4vK6VsGFpCTXWLC0Mh5ulirYCIxTnHnj/N2PBcAEtzV13FxcjZOyDkzm7r/CCBbyLHxwOgUmKajphYPZ+nludfHNB+' +
                     'ui2sjhpCndQCTnu+cjbcKsL/oBDc9fTg4PpxSY/5890+xqWEfJ4mJkwynQsuYsGt7Om3OWMgzmKCr0axK250Kq1tpXLryH' +
                     'B/PpvN61KpYA6AubKZts7r/GnTe4Pt2z5fHur/RBy+2SOB4Egtl8vUkppSVH76faYyGhKRV6ceSfk+OWO+bY18fNWSxc/V' +
                     'HLF9J3XXyOUilRw/uDntPR8Qps9A5A2InLWJz9Ky1sqG4Y/Lw/1v3a+hUp1X3tLefYtKpfM2DNZbmMzqU0/dVJ9L58OZ3G' +
                     'w2q1AouBZvyoBOjSdXRBwpxcQsNgg/unLg/veuWrL4uWw2rwFQqVQy+zWTVCxaFIs2m48+f+XQ4q+Rc//FSgGxOpwAdtaC' +
                     'gHPmtrefCEBqCZnX5EzlNYpFe8af/dn0lo6eYfa8PJyDc+aTqwcGfhMXUGoHSR2+oVBel0p9JtPaebs3pekD4WgdubF0gO' +
                     'g3YsyVI8P9Q7lcTpVzOVrbVziQ8SatLZXcvHmLvGMumfsj5fsX2SCQ2MmbzLRY9n3fGVr2ysonn801N3O5XJZXLbXlMtau' +
                     'Lbkz27v/0Jsy9VvM+hIAsCb43qqhgb+sV80JDksnK7rQgslc09Wq06mPh5XKOHKV1kqcW2ckvHT1ksGV8xYt8oq9veHBMB' +
                     'fz1q07qrojeEj7qYtNZTQAEQsgcWKEJhhkIWKAXRbAd+oduX1J6MRFDpvNZvUrJ576SWJ8FiDtrAlB+K2nsBAAFZNyZx0O' +
                     'PwnO57l8992S6Vg4ixV911mrSURFySVxrLWC4Nc2HH3n6uHhkWw+r5/44hfNQTh1Cnff7Wae2XIFKX2VCwPNnt+kPI+ZVS' +
                     '1jFqVFxQEkFCVQWKzMeOWKy7+6tq/P7ctzyAJqbV+fLZfL0tLePX/06GMeUL7XIcYQgJCV8sXaK1YM9j+Zy+VU+e673WGf' +
                     'yUrUTKate5n2vawJA0sgBREhZgHzVmPsJauH+5YfpAzRpPiDtmtPrrKdzcTnOCdvJpKzIDiZPd8noigbZi3EOQEQCNk55c' +
                     'HBNXusMtVJLADMbu35faXpUwD1EBPiuB/KT2lTqXywvKT/a7t7DnQ4kju7vfNaz2+611Qrpi7ONdrzdFgN37dqSd8/zZu3' +
                     'yFu+/ICr5b33GfJ5veGFF05m4XPF0VsE7mwALSD6PX/aNIRbt35k5VDfnbsgIwp5MhlJyD+7u/sU69T1gLuetTfdBoGIiC' +
                     'NmKM9TNqjcPDI0cPuewq/DiWBCPk9zn3tuhiVvFTMfF8eYLCJWp9PKVipfHBnu/8skuN9V8eEg1GIJ+TzlymVavz5Dpeay' +
                     'TFZMyNxwwzS1ecfp7HnnmaDyysjwwMN110bI5TgHoN4xyrR1n0OKP0hAK2vvKBsGEBELESImKM9nUw0+Xh7uu2PS53C4El' +
                     'xTze1df69T6ZtNpSa9lrVWztgnj5118nnN5bLsVVfFwS+415G+niaNwUUIV121E6lnXHvtdB3gUoLrEaI/VlH8DHHOEJES' +
                     'iGOlFQTO2fCD5eGBezK5nF8uFoM9VaoOD4IjMqSlp+dEhLIaROm6PighZpB1b1kxWektJrKlo+cCYr5TnDix9kPlJf0/OZ' +
                     'Q2Orm2eevWqQ3VqjrlxRdN/bWc1dEx1UFfAML7IHgPaX0SALgwjNKucY5CRKxOpbQz5r9dGHaVHxj8Yba7O13q66vM7lhw' +
                     'vodw/dODgy/sKpFyWIRJ2WXgEmAkdB/WqfSURHoFYrWfVqY6+vXy8EDkVBWL4wm75RYBwPLcC3drP3W2cxaOsPSMXFu2VB' +
                     'xecZBJJuRynF2/nkrNzYJCwS6PkiDhWgBnti86wePgLSL0Hgu5lJU6mZjhjIENwyS1yASoKLcOpVNp7cJwaRiOLnzmgQde' +
                     '/sNcrqnU1zfa0t51qYi95Iyg+qmndyPFh4MEU3yipxinnmHFvyfOCkBERBBgCxl1xsoH7l2fVJMmU40tz71YJk/PkjA0pH' +
                     'UKgl+bavDO1Q8NrpwsAbA/1XJ22TIuNTfvZIfn5RbNCJrM2eLc2wR4O4BzWOujiCjqNLHWCZEjkfH1ZiLRvq+sMTvg3C0j' +
                     'Q323A8Csd9+YWrP0zurs1p4/IXZ3+IG6+Mni/a/sLg16yAlOJCzT1t2jU/79SV03SdabSuUfysP9H9sVScnXW1q7r1Fpf4' +
                     'kNAiMipDxPiXP/HVTCP3q2OPjCfiR5TEonaIZM7oZpakowx1lzEZFcLI7OY8XHk9JxA4GBiNg4McJ1HSM1YlWUoYNz9lti' +
                     '8any0P3l2FO2AKSls+cGIv5Ha93Fq4YWP7an+zrkKjophhPkWhlrsRACKRsEFWvpy7vK0iBxVnI5NbKk74GWtq7ZKp3+rK' +
                     'lWjTWhVdp7g5fWj2ZynW8rFgf+6zWRnMupGqnFoi3Fh3PTSafNdbAXCfjtcDvOE+E3KN+HOAfAwhkjYo0lUNIPpgiIukri' +
                     'ShiJEHsegxlizE/h7OdGhvq/CQCzbrwxVbqzUJ132WVTqjOP/YI3Zdr1wbYt160a6n8sm83q4kSTdVhJcOIgdXWdBkur4g' +
                     'NHAljt+8pWqt8aWdJ/+d4Qk2iClrbu+1Q6tcBUKgYEUp6vnLUrQlN5+7NLlmzYx/6ocU19kZTmfE5NzQrhMnF4JzPNZu0B' +
                     'InDWQJyVKJtFVNc6hAnetBPAEbNmHcmYM/ZnQvKl8sDiBwC4+hAo09r1FtLq7tS06fMqmzfdVh4e+PTehEiHXIJj58rB8r' +
                     'uU73smqBhCnNgQASlaDID2JodbKpVsLpdTCHdcVyacrFOpt5ugam0YWOX5c32kHj3rfR2XPl0cXL9Hkusa5xNyozowXSmQ' +
                     '95Di32dWEaHWwgRVA6DWqEeTPVcRJwRHAmatmbRmGwSjYu2/wlHvyND9/wxAooaE9brU2xue/d7uo+10+hSAj+imKanq5s' +
                     '13lYcHPp3NZnWpt9fsnT05tMGvQrFoM21d39R+6vIoLQkmZnLWvhKQnbVmaGgL9raWGodbc9vajhb2HyelZltjLESgUyll' +
                     'w/Dp0FYv2Y0kj5PYc3ILjrNputw5t5AUn89KR6QaUyNrnB2dSGnkQQsBipQiVhriLJy1T4Pom07J0KrFY/Xr+KAaADS349' +
                     'pugXyGPe80EOCq1S+sHOr7q/i693q64pA7WfMWLfIq26pl0mpW9ODIac/XJqw+XB7qv3KfW06TQ5PrmMVp7wkAx4q1TgCn' +
                     'fV9bY39RrW5715pi8ZVxaq7u95xxdfcp2lMfIEgPe/p4cQ4uDEUIdrekRo6SAzDWSx17zICsAfh7TtzDLdXtpZpmSBIWMR' +
                     '8tnT1XEfAxYnU+QBBnjXPyifLQ4i/uK7mHluDY/mY6OmaRqDIALzr1YnUqrcNK9S9WDffdmc3m1b62utQ867buC0mrH4qI' +
                     'gnNclzR4Mgzc+555sO/FSB2DS6WCmdvWNlNU6iaB3Kg8f4YLQzjn7IQe6V1JqSalwEpBROBMuIlAPxXID4T1smDGlKeSea' +
                     'dcLqd+OXMmJ4fr7O7uo43lKwG8n7X6Q7EWxAwR+Y2YsGdkeOCRVxvPHzKCx5yizsvYT30nbmJTEHGkNYuxF40M9z3+apvG' +
                     'kyT87Gu6Orx0asCGgQGgRcQqz1POufUk7qaVg31DANDS0fOnIP4H5elTbRDU0oSTPKOxJvialDJcGECAZ0H4D4g8Qkb/+8' +
                     'oH7v2fye45+f+WjgVnE0mbCNqU558Y23SrfF9Za0u2Gi5a/eDAs6+ln+sQOlnzAZQAcIaY4+dGQszsjN3OTkfdkMWH3Ks5' +
                     'h6VSwcQqeDDT2nmqbmoqJNUpG4aOlWpmzx/MtHW1ArSWlbpBRBBn0VRSxZqofolIK60VQHAm3CLG/JSI/sUp/QPs2FyuU7' +
                     'djzlqUBHGlUsmccXX3KdqnPyFBq0AuYO2zC0PYaqXKnpeCUtaG4d+NDC6+JXG6isVX36x3CAleFusQeROSnjURgVJEzq57' +
                     '3bo1v3mtSmZ5b6/JZrO6tGTg1kx712ztp64xQdUSkRJrxVoryvffQ0Sw1aoAkInECmCTOJWY2YbhDjHmh46wxGNXeqq//7' +
                     '93eQGFgisBLpfLqSIgmdbOv1G+/msQTYEAYkKYasWwUlql0ilnwmXOuo+Vh/uejLNkXCy8thakQ0ZwrSdZ6PU1n4FIiBmO' +
                     '3UulUsnsh4qQlEoli3yem9atW1jdHpzNyjtTTOhim0ouDKPMUkQs1UssACjfjyYTjX3SOdenlfzTU319L+7KwTtr+vTZpm' +
                     'IvQJP+RvneezdGYV4m+lyFNGs9JRzdUSFiL6qjsBLQL40Jbi0PLO4bp8qj7kgckQQnmSkhHB0nsCgZCSMnG4BoxULxtf8q' +
                     'yZXLXCwWd8xp7byeFP9owlNTOxtZsUnKEM48ai3fWQ62fXcyX6Cla9FpZMO3CrmLCPRHzmA2gA+Ut2zZPHZAlyUuzxpxTk' +
                     'Cso6xWnNMKzftGHuh/Oum6LBX2X//0oVPR8ekkYCpEkkA3+gvJNmD8tMFrOkzFos3lcqq4ZKDU0tb1OHv+hdbETt3OsDqV' +
                     'Us6YlbD465VDi7878R9k2hdkmNEK4F3igrPY91IiAuV5CHbsyJeH++6p1z6JthJLm0RL0phXi+2NYp3L5VSxXMb+nkI85L' +
                     'loOUj7L5LDQqS+RoovQigS54NrFwJmF7XDmLs2B9tufqlYHE28+BNzuaYZ/tR2EDoI8lb2fF+shRgDG1QD1r4Ktm9/rDzc' +
                     'f2s2m9elyWznZLNJRCATeklOfX/f9+HQ+D5Jrnb/h2+JLU5t9h421covle9rQAIBrIiEYAJrrWwQ3DwydP+Np61fH8bib+' +
                     'e0L+iYkZ72C+V797DSWRHxTaViXBjGSQfS4qwomI8AQKm5POmWnqjD9uDi0BNMFI4jO1LcM8c5YvtJWQDA8kd6d8ChVZzb' +
                     'oNJNvvI8pdNpj1hZF4TvHxnqu33eokVeqVQymdaut8zpuvYx8tQAEZ1hqlXrTGhr3nbEmFW+z9aagRXDwytyk8TtNe3h7B' +
                     'SKq0hj9ytg7VUi3Z+R3x2C411VgGxCVNhPxvAhhOOiEPih/dtTVSg45PNcXtL/k7C6/TwXBLeJs//PheYrLjQXjgz33RuH' +
                     'V7alc8GnSat/J+aLbBhaMcbFiY9xyQ8ClA3DUJP399hlWXN+dKeM4yKzIDWSnXNGIdxyoB7zIbPB2WXLokqS0MtxFlAgwu' +
                     'IcIHL8vEWLvOWRdO/f/RYxyc8UCi8C+PTEb5/Z3n6uZv9L7HkX22oV1rnIGaPJLAmM9nxlgurSFcOLn8nlcmrSuDXJ6UBO' +
                     'HKe8mAHnfutNqW6sdzx/R1T0/DiFKy8lA5cgInEORHzC6OjoCbXE4P734F02m9WzbrwxVfOMcwuPmdPR8380pR4npS42lY' +
                     'pJKkG7MzACIYL07i4jU4rnkUjoTHHRDpC4kR8Q/Hp5b3FLvRk58gnO57mp6T8VQEIiP0Zy0wDBOcdapcnRmQCQu+qq/XWN' +
                     'lGyvi50us+bOO6vn5BYcN6ez5yZOu+Xs+Z8WsWkbhnanxMfO7r9jpdgFwXMSVr4fh2NuF3GajaYeJSPOgkRIiATEEMgaAH' +
                     'IgPOhDQTDlcjmFQsEtXbq0Oueaha8n5nc4Z6PhrUhkLDE7EcyLHJQMvVZCc9HDExQKLkn2z23vObels+fLYVpWsPbuAHCK' +
                     'qVbsXkhtnICDsNZEoLvKxWKQzebVpBIYJy/W/97aN4HoFLFJs4cIMYGA5QCQffX3eVjYYEqWhBSLRTu7re1kZv9GwPWQ9l' +
                     '7ngsBJFCOaqJTKDOAyAJ8rzYdDaa/I3HnSoFCQZJVBNp/XG9esPUuYLoW4KwQ4T2kfzoTRAhcR2sUY6C6kV7OtVl82aXU/' +
                     'AIqb4ibxNcAlQIjkbcpPaRNUDQEaAhZr4WKCm5vLcmQSHIcNpVLJnJrLzWhKT/sEQNcrrY62QQAX2JCV8lTcm2TDECYMXi' +
                     'LCi5lczi8XCsFOZMZdjUDctFcoOBQKMiGtSbNbe2Ypj84X59624fm1FxLRGaw9JAvS4hYhNakTFS9XSVYuTXCunPK0NhV7' +
                     '2zP33bd1d7Xa5lpMLJfVCbgwM9vQbFfO+/lu1ft+TzLsX3Uc7ZCIGtUWgegvSOlZLgxARGDPi4vjZh0Ij0FomSj3RFMq9e' +
                     'zy3t4dgBByV3E2XppSKpXsZGpw1o03prwNW0/SGhnr5M0EmgPILBE5Q6dSHgA4a+O9VzA7taxOSK3Fsa12YWghsgPM0xOS' +
                     'k/lka8xTTVNT5112wgm2sOv1wwRAMrmFx8A3a5h4poiT6IB4yoZhqTzUN/9AjtnoAym1xWLRtrR3XQrWt5HiedF+SAfWGu' +
                     'LsGhcG3wfRd9NV//Hlxd7N9RYul9ukikWyKMLWa+izu7uPDgM5nRTNheAcABls3HoqWE6E8rT2GHACcQ7OWpggiOxqTCoB' +
                     'erKQJ26KE1ZKaT+lbVD9qYgsY6Wvk6gHluMEB0TEEOi65b294fLIvk9KTmKW4JlW7fszk57vyP4yROSROjV+ZBCcjJdEud' +
                     'spnyfmD7OO21is3URE3yNC/1aWH63t66uMhSn1vUkkxSLs3LbrZzoenQ3BeYjmb1uMwams1XFRuymN20oXlf5E6lYRUs1h' +
                     'IppcXgEHEWKtWSkFZ8w6F4a3B9sr/+JPbXoEhKNhnYsjG+Ol0164Y8cny0sGfrqHdl4qzZ/vMs3NPiA3io28ZxBFPd/Vas' +
                     'gW34zMzF75GYdeRdcmBKM+3q94qfS51oRwxj5HkPtJ9MCKoa+/lHiXO60oWrjwGArseSR0sYi7EIQWYnUsKx3lQZJtsc7F' +
                     'vceI9k1Osj5ht+UNUK3lJjETYuwqgdy3Tcld6U2bPO+o1/2CtDrNBoEjIhaRUKfTnqlWHywP9V2zpx6p2sRGe1er9lPDYx' +
                     'MbsMrz2QXV740M9//xgd5jSfvb3ra0dnWxp/tUKo1wdPRpJrqLJBh+enBwOzC2uCv5wTldC88C7MVicQmAC1irZmI1bkJe' +
                     'ILZueSjv43ULACdR2SpqXx1z6DYT4VEQDU0b3bb0x8XiaKat7Rzm1LdI8UlRSZGUiIQ6lfJcEPxH5Zij3r7mmGPCPaz9J+' +
                     'TzNGvjRi+1cfMKUnqWGCO13LXnKama96xcsvifD+Dc1H4iOO5FBiBz2rtv96ZN/3iwffuLAve5YOZRfWvuvLOKfJ5xyy2S' +
                     'lMsy7QsypOhyOHcFgHOV51ONUGujnuOxUY99I7O2URYgEUXMREqDFEOMgXP2ZRA9DvB3fA8/+MV9972c/HBLR9f1xPoOAE' +
                     '3O2kjiYnJtGP5MqtvfVS4WN+7JKao1FLZ2/pVqavq8qVZsvLjNstbsQvPkSLjjPBQzAhzYGWbaD+Q65PM894WXHgDTnzvr' +
                     'PmE2b/jqM9/+9tZ69RNthtWXAdwNce9Uvh8t+TQGAjG7HfWYhMRon3Pd+l9A1RZ5c/QxYg3EuY0CrATR43Du+1WYn8fN9D' +
                     'XMbW8/Uci/jT3dacMQcFG+NFLLTZ4Ng8fYVt+7Ynh4097s2ECh4Oa2LThVFJ4SkSnxrHNtJMcE1Vx5qP8bB2O0lV4rudF0' +
                     'uvs3VnrUBKZ71ZLFz9VL9azcguPSTfx+iFtISp8GSDTkLGLiXY+T2c7EV3ITSORoqpRBTBGRRJHHbAwgshGEFyF4SgjLFa' +
                     'sVfqjLy5f0bpjsFs649trpXiALhORTSnuvj+0kxwcIKpViGwYPbdy0YcG6Rx7ZsRfhTJTQaW6WjD+1pDx9YdIOHIVXnrIm' +
                     'XF4OdpyPun0cBxKv0ouObvT01kXH6krlYWL+pxV9Xy8kGaNSoWBmtbcflWL/WkD+krU60YUCGwZjQ8713Yvx6CQSDziylS' +
                     'BmVU9ibJNHRewrzuElIjwHcJlJnmGNX4bWvbx6YOA3E682tvsmsZmRM2e6KJAPs6dPi5IeEblx37QWEdhq9TMjQ31/N05b' +
                     '7QbzFi3Spd7eMNPaebtO+Rea6tisVZSHdUJEH0HSQnQwUoiv8mfk9NbWYzX5t8LZb5QfGPxhXZhDc7p6FonQJ5TnvcmFIZ' +
                     'y1pm4za70nG00EMCMmFHACGwaAYB2Y1gKyBoLVEFoFwq8Yet0WHW6oD7HG2b98Xr9SLnM5kxm3yvCsjo6pIvpcIboSwNXs' +
                     '6eOdtXDWWhKhZNpP+T6cCX9hjbl51fDAo/XaaA/kest7e8NMW+d1yk/3urjRPr5bo9NpbUYrXy0v6b/+QDtWr41gEcpc9a' +
                     'GplNp2hQnp3555sO/FsX3FHRcQebez1he4yL6aOA1I9dN1pBSPjU0aOOd+RYSVBPo5EZ4yoSuTnba2XLx7267Mw7x169To' +
                     'pk3UAtjJHlYml/NFTz1ZeXQ+nFwKyHyweiNHsS6cczY5a0SkVSoFF4ZbAHyu8sq6L61ZurS6tzaydv+tHdewlxp2zjqIcO' +
                     'TniWOt2Vn3vE3xHzyzdesOHMRX89Crkd451yx8vbOp7XUEUEt7d56Y/4ZYsTVB7X0JceO4Yq2JatN15mUA/8Hgf4XIz/yp' +
                     '/uooNTl5uLFbCcrnuWXt2jeIpZMZcrqItEBwJghniuCN2ve95CCJtSKAiRMfzKwUexo2CHYA1I/QfmHkwf7n67Nxeyyi5P' +
                     'OqVCiYTGt3O3uqX5wDnEvicgGRJWYWYy8eGe57/GBK72v1ogmAtFx99RvJn7qYPf12W63WJtaFSJhZsdZxHtitJKalBHxv' +
                     '1AU/mejJJkNgwLKkgBA5ae++MTXl2E06dO5YTd4JQjheCKfA0ZsIcqoDzSLIyaRUU7T5dXx2S0TCaAuiJLadWes4s2Z+CV' +
                     'BRhL+eLA6PpdbuUcLqDkCmrXsRa/U1cU7gXPICCCReeDg6+tFVB+/dDa+d4EQlnXF1xwXa9x5UWp9oqtUQREwizJ5HxAwX' +
                     'ml8L8M9E6L/y1JMeK9Q7KclKhDoyJw032nvOFabvisgMImoiper3ldSyW3BOhMjGKlckfn9DvX2XyOauI9AT4uSh9Bb/O+' +
                     'PegbR3ni0lE4+z3v3uVOrY429nrW90YejqMmoQkdBravJMZfSekaH+RYdqpdM+E5xMumXau/6cWQ+AkBZjAgAe+z5BBM7Z' +
                     '/4TQPezSD68Y/sqm+gRAc3Oz7PXr4ZIR07aOD+lU013xghWMNa6N5ZyTeyHmqJhhHZw1ARH+B6DnQfQzJ+4HTdXUE/WFjb' +
                     '1eHC5C2fnzVUJSprXrLeSpf1Tae7OtVm19UqYurdlfHurr3qs3pR0OBNfyq63d7crXg85aEWeN8nwPAojYHzmRz5cH+5aO' +
                     'y09HBc9XdYN1+e0Pq5R/Z7RG3znaudwnIDIk6Be4h1h4HbHeVvXdhmfuu2/rRPWaG6vByp5SsHG91gLArNyC49JpfFSAv2' +
                     'JmL14OqusC+NBLN3mmWvnmyGDfFXvrhR9ygmuS29bdozzvfmdNAMBXngcXmp8IyWdHBhZ/b0Juer+c2vrEPSt9D4Cpzhgz' +
                     'YRIwmlQg+i0gRXb42oqhxT+vPyi/2LpVzzjpJHfqpk2uWP8qWRHgllsI5TJFjQTzowpPnVS3XN3zRvJpAQTXs+cdb4NqtH' +
                     'W+3l4QiUqllAuDe1dWtn8gTmYc0lfV0r5I0ZnXdLxT+/5Ssday53li7UYn7rPlwb67kaysn/ByyP1ahiyVzOxrOs5XnvdN' +
                     '1voEU63uNKRNRGDfT+ztjwX0ECv3LytPOeXZfc0cnd3dfbQL+a2i5BoRvFd5/gxnQjhnLYHqVbJhpTQpBRuG+fJQ361113' +
                     'RI32FIe20HOzpmMeknQOpYit7W+UAYuE8+82Dfi4jbaA7069tqWiTXeRKn9T3seZfWTePXqUnYMWdPwQZVA8KzJPSEAD8X' +
                     'wmoitU5TuGX7qAr8KU6RyFQJ8TpmdQpIZgtwvkDOVUq/PnIYQzhx9W97mbiV7n9g5YaR4cUPH0qbu68EUy6X41/OnJmqbK' +
                     '88rpumnGMrlY2AfCRZfXDQvcP68KRjwceYpFDbp1z/wqoIViLXVrNSoCSMGntp5SgJQgEUCClm1qw0wHWp0WjdoIwrhER7' +
                     'rqKtdKwg1nxbgh0fHnnwwV8d8gWo+0JwrezV3nVX6qijP1TduqXkLK5btWTxc/sQVux/1JUfZ7e2/j7r1GcJ1MFKw4ZB9L' +
                     '5BQNW1cUwsI3LSUEd1bQCo7d+o5cR5gqp1grEXVFoTPi2Evy333/+NelOGwwi0J7s7u737ktTUqY+a0cq9qSZ9/fLe3vBw' +
                     'OaX1D3ROZ+dbBfpmAi6PXhoZjtWWZdKqlezxedSKIFHOnD0PRARrzGoWfGl0w7q+NUuX7lTvPhIIjjoS/nOj579uyzpmWr' +
                     'xyYPFNdalDd9jcwQTHrqW95zwQXQuSy5jVicQqVskWEOeEkuY2GpeRq3Eeh9dJkiTp/bJhNSTix0C4fyu5byTFjsNRavdI' +
                     'cG3PVHv3bQAdOzK0eNFep/AOJdFR2OMAYFZ7+1E+6QsI9A5A3gZQCyuVIlZ1lErdy+jix0GRIo72Tsp/EeOnTuhHSujRFU' +
                     'P3PTPhGbnD9nnskuC48+/MqztPZ0XvLw/333yk3ExCdP1bS8bKhe9/kyVzugjNIievB+F4QKYKcBQgVYC2k8hmYl4Hoeed' +
                     'ds+O9WePPZvcVVfxEfMsdicJLW3dF2ZyN0zD2D6JIw2Uy+VUMmz2Wrz2bDar98dr6Q7rTNYRj1iyx4bZlgEASs3NkkMyhT' +
                     '8/mmpdNr6i9bv8WP73HIAGGmiggQYaaKCBBhpooIEGGmiggQYaaKCBBhpooIEGGmiggQYaaKCBBhpooIEGGmiggQYaaOBI' +
                     'xf8HDL/M688E8zoAAAAASUVORK5CYII=')

    return f'''<div style="padding: 24px 40px; text-align: center; border-top: 1px solid #e5e7eb;">
<a href="https://smoothschedule.com" target="_blank" style="display: inline-flex; align-items: center; justify-content: center; text-decoration: none; color: #6b7280; font-size: 12px;">
<img src="{logo_data_url}" alt="SmoothSchedule" width="18" height="18" style="vertical-align: middle;" />
<span style="margin-left: 6px;">Powered by SmoothSchedule</span>
</a>
</div>'''


def render_unknown_component(props: dict, context: dict) -> str:
    """Fallback for unknown component types."""
    return ''


def render_email_html(puck_data: Dict[str, Any], context: Dict[str, Any]) -> str:
    """
    Render full email HTML from Puck data.

    Args:
        puck_data: Puck editor JSON with 'content' array
        context: Tag substitution context

    Returns:
        Complete email HTML document
    """
    content = puck_data.get('content', [])

    # Build body content
    body_html = ''
    has_layout = False
    has_branding = False

    for component in content:
        if component.get('type') == 'EmailLayout':
            has_layout = True
        if component.get('type') == 'EmailBranding':
            has_branding = True

        body_html += render_component_html(component, context)

    # Auto-add branding for free plans if not already present
    if not has_branding and not context.get('can_remove_branding', False):
        body_html += render_email_branding({'showBranding': True}, context)

    # Wrap in layout if none provided
    if not has_layout:
        body_html = f'''<div style="{EMAIL_STYLES['wrapper']}">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="{EMAIL_STYLES['container']}">
<tr><td style="{EMAIL_STYLES['body']}">
{body_html}
</td></tr></table></div>'''
    else:
        # Close layout tags
        body_html += '</td></tr></table></div>'

    # Wrap in HTML document
    html = f'''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Email</title>
<!--[if mso]>
<style type="text/css">
body, table, td {{ font-family: Arial, sans-serif !important; }}
</style>
<![endif]-->
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f4f4f5;">
{body_html}
</body>
</html>'''

    return html


# =============================================================================
# Plain Text Rendering
# =============================================================================

def render_component_text(component: Dict[str, Any], context: Dict[str, Any]) -> str:
    """
    Render a single Puck component to plain text.

    Args:
        component: Puck component dict
        context: Tag substitution context

    Returns:
        Plain text string
    """
    comp_type = component.get('type', '')
    props = component.get('props', {})

    # Substitute tags
    for key, value in props.items():
        if isinstance(value, str):
            props[key] = substitute_tags(value, context, escape_html=False)

    if comp_type == 'EmailHeading':
        text = props.get('text', '')
        level = props.get('level', 'h2')
        if level == 'h1':
            return f"\n{'=' * len(text)}\n{text}\n{'=' * len(text)}\n\n"
        else:
            return f"\n{text}\n{'-' * len(text)}\n\n"

    elif comp_type == 'EmailText':
        return props.get('content', '') + '\n\n'

    elif comp_type == 'EmailButton':
        text = props.get('text', 'Click Here')
        href = props.get('href', '')
        return f"[{text}]: {href}\n\n"

    elif comp_type == 'EmailDivider':
        return '\n' + '-' * 40 + '\n\n'

    elif comp_type == 'EmailSpacer':
        return '\n'

    elif comp_type == 'EmailImage':
        alt = props.get('alt', 'Image')
        src = props.get('src', '')
        return f"[{alt}]: {src}\n\n"

    elif comp_type == 'EmailPanel':
        content = props.get('content', '')
        return f"---\n{content}\n---\n\n"

    elif comp_type == 'EmailHeader':
        business_name = props.get('businessName', context.get('business_name', ''))
        return f"{business_name}\n{'=' * len(business_name)}\n\n"

    elif comp_type == 'EmailFooter':
        parts = []
        if props.get('address'):
            parts.append(props['address'])
        if props.get('phone'):
            parts.append(props['phone'])
        if props.get('email'):
            parts.append(props['email'])
        if props.get('website'):
            parts.append(props['website'])
        return '\n---\n' + '\n'.join(parts) + '\n'

    elif comp_type == 'EmailBranding':
        show_branding = props.get('showBranding', True)
        if context.get('can_remove_branding', False):
            show_branding = False
        if show_branding:
            return '\n---\nPowered by SmoothSchedule\nhttps://smoothschedule.com\n'
        return ''

    return ''


def render_email_plaintext(puck_data: Dict[str, Any], context: Dict[str, Any]) -> str:
    """
    Render plain text version of email from Puck data.

    Args:
        puck_data: Puck editor JSON with 'content' array
        context: Tag substitution context

    Returns:
        Plain text email
    """
    content = puck_data.get('content', [])

    text_parts = []
    has_branding = False

    for component in content:
        if component.get('type') == 'EmailBranding':
            has_branding = True
        text_parts.append(render_component_text(component, context))

    # Auto-add branding for free plans if not already present
    if not has_branding and not context.get('can_remove_branding', False):
        text_parts.append('\n---\nPowered by SmoothSchedule\nhttps://smoothschedule.com\n')

    return ''.join(text_parts).strip()


# =============================================================================
# Main Render Function
# =============================================================================

def render_email(template: 'PuckEmailTemplate', context: Dict[str, Any]) -> Dict[str, str]:
    """
    Render a complete email from template and context.

    Args:
        template: PuckEmailTemplate instance
        context: Dictionary of tag values

    Returns:
        Dict with 'subject', 'html', and 'text' keys
    """
    return {
        'subject': render_subject(template.subject_template, context),
        'html': render_email_html(template.puck_data, context),
        'text': render_email_plaintext(template.puck_data, context),
    }


def render_custom_email(template: 'CustomEmailTemplate', context: Dict[str, Any]) -> Dict[str, str]:
    """
    Render a custom email template (for plugins).

    Same rendering as system emails but for CustomEmailTemplate instances.

    Args:
        template: CustomEmailTemplate instance
        context: Dictionary of tag values

    Returns:
        Dict with 'subject', 'html', and 'text' keys
    """
    return {
        'subject': render_subject(template.subject_template, context),
        'html': render_email_html(template.puck_data, context),
        'text': render_email_plaintext(template.puck_data, context),
    }
