"""Dataclass models for TokenMoulds document intermediate representation."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Dict, List, Literal, Optional

from tokenmoulds.design.engine import LigatureMode, NumberForm, NumberSpacing

if TYPE_CHECKING:
    from tokenmoulds.colors.palette import ThemePalette
    from tokenmoulds.design.page_geometry import PageGeometry


# =============================================================================
# ADVANCED TYPOGRAPHY MODELS
# =============================================================================


@dataclass
class OpenTypeFeatures:
    """OpenType feature settings for Word 2010+ (w14 namespace).

    These map to w14:ligatures, w14:numForm, w14:numSpacing, etc.
    """

    ligatures: LigatureMode = "standard"
    number_form: NumberForm = "default"
    number_spacing: NumberSpacing = "default"
    stylistic_sets: List[int] = field(default_factory=list)  # 1-20 for ss01-ss20
    contextual_alternates: bool = True


@dataclass
class CharacterTypography:
    """Advanced character-level typography settings.

    Maps to w:spacing, w:kern, w:w, w:smallCaps, w:caps.
    """

    tracking_twips: int = 0  # w:spacing - character spacing adjustment
    kern_threshold_half_pt: int = 0  # w:kern - min size for kerning (0 = always)
    character_scale_pct: int = 100  # w:w - horizontal scaling (1-600)
    small_caps: bool = False  # w:smallCaps
    all_caps: bool = False  # w:caps
    opentype: OpenTypeFeatures = field(default_factory=OpenTypeFeatures)


@dataclass
class LineSpacing:
    """Line spacing configuration.

    Maps to w:spacing/@w:line and w:spacing/@w:lineRule.
    """

    value_twips: int = 0  # 0 means auto/default
    rule: Literal["auto", "exact", "atLeast"] = "auto"

    @property
    def is_default(self) -> bool:
        return self.value_twips == 0 and self.rule == "auto"


@dataclass
class ParagraphIndent:
    """Paragraph indentation settings.

    Maps to w:ind element attributes.
    Note: first_line_twips and hanging_twips are mutually exclusive.
    """

    left_twips: int = 0  # w:ind/@w:left or @w:start
    right_twips: int = 0  # w:ind/@w:right or @w:end
    first_line_twips: int = 0  # w:ind/@w:firstLine (positive indent)
    hanging_twips: int = 0  # w:ind/@w:hanging (outdent, mutually exclusive)

    def __post_init__(self) -> None:
        if self.first_line_twips and self.hanging_twips:
            raise ValueError(
                "first_line_twips and hanging_twips are mutually exclusive; "
                f"got first_line_twips={self.first_line_twips} and "
                f"hanging_twips={self.hanging_twips}"
            )

    @property
    def is_default(self) -> bool:
        return (
            self.left_twips == 0
            and self.right_twips == 0
            and self.first_line_twips == 0
            and self.hanging_twips == 0
        )


@dataclass
class ParagraphControl:
    """Paragraph flow and control settings.

    Maps to w:jc, w:contextualSpacing, w:keepNext, etc.
    """

    justification: Literal["left", "center", "right", "both", "distribute"] = "left"
    contextual_spacing: bool = (
        False  # w:contextualSpacing - suppress spacing between same styles
    )
    keep_with_next: bool = False  # w:keepNext
    keep_lines_together: bool = False  # w:keepLines
    widow_orphan_control: bool = True  # w:widowControl
    suppress_auto_hyphens: bool = False  # w:suppressAutoHyphens

    @property
    def is_default(self) -> bool:
        return (
            self.justification == "left"
            and not self.contextual_spacing
            and not self.keep_with_next
            and not self.keep_lines_together
            and self.widow_orphan_control
            and not self.suppress_auto_hyphens
        )


@dataclass
class LocaleSettings:
    """Locale-derived text settings.

    Maps to RTL attributes, line breaking rules, etc.
    """

    rtl: bool = False  # Right-to-left text direction
    # Line breaking rules (for East Asian and Latin)
    ea_ln_brk: bool = True  # East Asian line break rules
    latin_ln_brk: bool = False  # Latin line break (strict)
    hanging_punct: bool = True  # Optical margin alignment (hanging punctuation)


@dataclass
class TextAlignment:
    """Text alignment settings for presentations.

    Horizontal: l=left, ctr=center, r=right, just=justified
    Vertical: t=top, ctr=center, b=bottom
    """

    title_horizontal: Literal["l", "ctr", "r", "just"] = (
        "l"  # Left-aligned (not centered!)
    )
    body_horizontal: Literal["l", "ctr", "r", "just"] = "l"
    title_vertical: Literal["t", "ctr", "b"] = "t"  # Top-aligned
    body_vertical: Literal["t", "ctr", "b"] = "t"


@dataclass
class TrackingSettings:
    """Letter spacing (tracking) by text context.

    Values in 1/100th of an em (hundredths of EMU for PowerPoint).
    Negative = tighter, positive = looser.
    """

    display: int = -20  # Tighter for large display text
    heading: int = -10  # Slightly tighter for headings
    body: int = 0  # Normal for body
    small: int = 10  # Slightly looser for small text


@dataclass
class FallbackFonts:
    """Font fallback chains for international text.

    EA = East Asian (CJK), CS = Complex Script (Arabic, Hebrew, etc.)
    """

    ea_font: str = "Microsoft YaHei"  # Default East Asian fallback
    cs_font: str = "Arial"  # Default Complex Script fallback


@dataclass
class SectionSettings:
    """Per-section layout settings.

    Each section in a Word/Writer document can have different page geometry,
    column layout, and grid settings.  In OOXML every ``w:sectPr`` carries
    its own ``w:pgSz``, ``w:pgMar``, ``w:cols``, and ``w:docGrid``.

    The *last* section in ``DocumentIR.sections`` defines the document's
    final section (direct child of ``w:body``).  All preceding sections
    are emitted as section-break paragraphs.
    """

    page_geometry: Optional[PageGeometry] = None
    columns: Optional[SectionColumns] = None
    grid: Optional[DocumentGrid] = None
    # "nextPage" (default), "continuous", "evenPage", "oddPage"
    break_type: Literal["nextPage", "continuous", "evenPage", "oddPage"] = "nextPage"


@dataclass
class DocumentSettings:
    """Document-wide typography settings for settings.xml.

    Default values are derived from a 12pt baseline grid via
    :mod:`tokenmoulds.design.layout_defaults`.
    """

    auto_hyphenation: bool = False  # w:autoHyphenation
    hyphenation_zone_twips: int = 360  # w:hyphenationZone (1.5× 12pt grid)
    consecutive_hyphen_limit: int = 3  # w:consecutiveHyphenLimit
    hyphenate_caps: bool = False  # inverse of w:doNotHyphenateCaps
    default_tab_stop_twips: int = 720  # w:defaultTabStop (3× 12pt grid)
    character_spacing_control: Literal[
        "doNotCompress", "compressPunctuation", "compressPunctuationAndJapaneseKana"
    ] = "doNotCompress"
    enable_opentype_features: bool = True  # w:compatSetting enableOpenTypeFeatures


@dataclass
class ColumnDefinition:
    """Single column definition for unequal-width columns."""

    width_twips: int
    space_twips: int = 0  # space after this column (gutter)


@dataclass
class SectionColumns:
    """Column layout for a document section.

    Maps to w:cols element in w:sectPr.
    """

    equal_width: bool = True  # w:cols/@w:equalWidth
    num_columns: int = 1  # w:cols/@w:num
    space_twips: int = 720  # w:cols/@w:space (3× 12pt grid = default gutter)
    separator: bool = False  # w:cols/@w:sep (draw line between columns)
    columns: List[ColumnDefinition] = field(default_factory=list)  # for unequal widths

    @property
    def is_single_column(self) -> bool:
        return self.num_columns <= 1


@dataclass
class DocumentGrid:
    """Document grid settings for baseline/character alignment.

    Maps to w:docGrid element in w:sectPr.
    """

    grid_type: Literal["default", "lines", "linesAndChars", "snapToChars"] = "default"
    line_pitch_twips: int = 360  # w:docGrid/@w:linePitch (1.5× 12pt grid)
    char_space_twips: int = 0  # w:docGrid/@w:charSpace (horizontal, for CJK)

    @property
    def is_default(self) -> bool:
        return self.grid_type == "default"


# =============================================================================
# CORE STYLE MODELS
# =============================================================================


@dataclass(frozen=True)
class ColorTheme:
    dk1: str
    lt1: str
    dk2: str
    lt2: str
    accent1: str
    accent2: str
    accent3: str
    accent4: str
    accent5: str
    accent6: str
    hyperlink: str
    hyperlink_followed: str

    def to_dict(self) -> Dict[str, str]:
        """Convert to dict for palette generation."""
        return {
            "dk1": self.dk1,
            "lt1": self.lt1,
            "dk2": self.dk2,
            "lt2": self.lt2,
            "accent1": self.accent1,
            "accent2": self.accent2,
            "accent3": self.accent3,
            "accent4": self.accent4,
            "accent5": self.accent5,
            "accent6": self.accent6,
            "hlink": self.hyperlink,
            "folHlink": self.hyperlink_followed,
        }


@dataclass
class TypographyScheme:
    body_font: str
    heading_font: str
    baseline_emu: int
    size_map_pt: Dict[str, float]
    monospace_font: str = "Courier New"


@dataclass
class ParagraphSpacing:
    before_twips: int
    after_twips: int


@dataclass
class ParagraphStyle:
    style_id: str
    name: str
    font_family: str
    font_size_pt: float
    color: str
    spacing: ParagraphSpacing
    bold: bool = False
    italic: bool = False
    # Advanced typography (optional, with sensible defaults)
    character: CharacterTypography = field(default_factory=CharacterTypography)
    line_spacing: LineSpacing = field(default_factory=LineSpacing)
    indent: ParagraphIndent = field(default_factory=ParagraphIndent)
    control: ParagraphControl = field(default_factory=ParagraphControl)


@dataclass
class HeadingStyle(ParagraphStyle):
    level: int = 1
    outline_level: int = 0

    def __post_init__(self):
        # Headings default to keep-with-next
        if self.control.is_default:
            self.control = ParagraphControl(
                keep_with_next=True,
                keep_lines_together=True,
            )


@dataclass
class ListStyle:
    """List styling with typographic best practices.

    Best practices implemented:
    - Hanging indent: bullet/number hangs, text aligns cleanly
    - Bullet character: mid-dot or small bullet, not heavy circle
    - Number alignment: right-align for proper digit alignment
    - Spacing: tighter between items than between paragraphs
    - Baseline: bullet aligns with text x-height
    """

    # Indentation
    indent_twips: int  # Left indent for first level
    hanging_twips: int  # Hanging indent (bullet/number hangs in margin)
    indent_per_level_twips: int = (
        0  # Additional indent per nesting level (0 = use indent_twips)
    )

    # Bullet settings
    bullet_char: str = "•"  # Small bullet (U+2022), not heavy ● (U+25CF)
    bullet_font: str = "Symbol"  # Font for bullet character

    # Numbered list settings
    number_format: str = "%1."  # Format string (%1 = number)
    number_alignment: str = "right"  # Right-align numbers so periods align
    number_font: str | None = None  # None = use body font with tabular figures

    # Spacing
    space_between_items_twips: int = (
        0  # Space between items (0 = tighter than paragraphs)
    )
    space_before_list_twips: int = 0  # Space before first item
    space_after_list_twips: int = 0  # Space after last item

    # Nested list differentiation
    nested_bullet_chars: tuple[str, ...] = (
        "•",
        "–",
        "·",
    )  # Bullet, en-dash, mid-dot for levels

    @property
    def effective_indent_per_level(self) -> int:
        """Get indent per level, defaulting to indent_twips."""
        return (
            self.indent_per_level_twips
            if self.indent_per_level_twips > 0
            else self.indent_twips
        )


@dataclass
class QuoteStyle(ParagraphStyle):
    border_color: str = "#CCCCCC"
    border_width_twips: int = 15
    indent_twips: int = 360


@dataclass
class HyperlinkStyle:
    link_color: str
    visited_color: str


@dataclass
class ColorAnalysis:
    palette_summary: dict[str, object]
    warnings: list[str]
    previews: dict[str, list[str]] | None = None


@dataclass
class SlidePlaceholder:
    kind: str
    x: int
    y: int
    cx: int
    cy: int
    text_style: str
    level: int = 0


@dataclass
class PresentationLayout:
    slide_width: int
    slide_height: int
    background_color: str
    placeholders: List[SlidePlaceholder]


@dataclass
class CharacterStyle:
    """Character-level style (no paragraph properties)."""

    style_id: str
    name: str
    font_family: str | None = None  # None = inherit
    font_size_pt: float | None = None  # None = inherit
    color: str | None = None  # None = inherit
    bold: bool | None = None
    italic: bool | None = None
    character: CharacterTypography = field(default_factory=CharacterTypography)


@dataclass
class DocumentIR:
    org_id: str
    locale: str
    color_theme: ColorTheme
    typography: TypographyScheme
    body_style: ParagraphStyle
    heading_styles: List[HeadingStyle]
    list_style: ListStyle
    quote_style: QuoteStyle
    hyperlink_style: HyperlinkStyle
    presentation_layout: PresentationLayout
    # Optional fields with defaults
    color_analysis: ColorAnalysis | None = None
    # Document-wide settings (optional, with sensible defaults)
    document_settings: DocumentSettings = field(default_factory=DocumentSettings)
    section_columns: SectionColumns = field(default_factory=SectionColumns)
    document_grid: DocumentGrid = field(default_factory=DocumentGrid)
    # Locale-derived settings (RTL, line breaking)
    locale_settings: LocaleSettings = field(default_factory=LocaleSettings)
    # Typography extensions for presentations
    text_alignment: TextAlignment = field(default_factory=TextAlignment)
    tracking: TrackingSettings = field(default_factory=TrackingSettings)
    fallback_fonts: FallbackFonts = field(default_factory=FallbackFonts)
    # Brand tone: 0.0 (conservative/formal) to 1.0 (expressive/casual)
    # Affects table styling intensity, spacing decisions, etc.
    brand_tone: float = 0.5
    # Pre-computed color palette with all tint/shade variants
    # Used by ODF emitters for canonical named styles
    color_palette: Optional[ThemePalette] = None
    # Page geometry for document layout (margins, page size, orientation)
    # Used by Word and Writer emitters for section properties
    page_geometry: Optional[PageGeometry] = None
    # Multi-section support: each entry defines a section with its own
    # page geometry, columns, and grid.  Empty list = single section using
    # the top-level page_geometry / section_columns / document_grid.
    sections: List[SectionSettings] = field(default_factory=list)
