# backend/app/schemas.py
from __future__ import annotations

from datetime import date, datetime
from typing import Any, Optional

from pydantic import BaseModel, ConfigDict, Field

from .models import ClsType, InboxKind, InboxStatus, Prio, Sphere, TaskStatus


# ---------- people ----------
class PersonOut(BaseModel):
    id: str
    name: str
    initial: str
    color: str
    is_me: bool


# ---------- tree ----------
class ProjectNode(BaseModel):
    id: str
    folder_id: str
    parent_id: Optional[str] = None
    name: str
    tone: str
    sort_order: int
    pinned: bool
    task_count: int = 0  # 자신+하위 프로젝트의 작업 수 합
    children: list[ProjectNode] = []


class FolderOut(BaseModel):
    id: str
    name: str
    tone: str
    icon: str
    sort_order: int
    is_system: bool
    projects: list[ProjectNode] = []  # 최상위 프로젝트(중첩 children 포함)


class FolderCreate(BaseModel):
    name: str
    tone: Optional[str] = "ink"
    icon: Optional[str] = "folder"


class FolderPatch(BaseModel):
    name: Optional[str] = None
    tone: Optional[str] = None
    icon: Optional[str] = None
    sort_order: Optional[int] = None


class ProjectCreate(BaseModel):
    folder_id: str
    parent_id: Optional[str] = None
    name: str
    tone: Optional[str] = "ink"


class ProjectPatch(BaseModel):
    name: Optional[str] = None
    tone: Optional[str] = None
    parent_id: Optional[str] = None
    sort_order: Optional[int] = None


# ---------- tasks ----------
class CommentOut(BaseModel):
    id: str
    task_id: str
    person_id: str
    text: str
    created_at: datetime
    edited_at: Optional[datetime] = None


class CommentPatch(BaseModel):
    text: str


class TaskNode(BaseModel):
    id: str
    project_id: str
    parent_id: Optional[str] = None
    title: str
    status: TaskStatus
    assignee_id: Optional[str] = None
    due: Optional[date] = None
    prio: Prio
    notes: str
    est: str
    delegated: bool
    sort_order: int
    created_at: datetime
    updated_at: datetime
    comments: list[CommentOut] = []
    children: list[TaskNode] = []


class TaskCreate(BaseModel):
    title: str
    project_id: str
    parent_id: Optional[str] = None
    status: Optional[TaskStatus] = TaskStatus.todo
    assignee_id: Optional[str] = None
    due: Optional[date] = None
    prio: Optional[Prio] = Prio.normal
    notes: Optional[str] = ""
    est: Optional[str] = ""


class TaskPatch(BaseModel):
    title: Optional[str] = None
    project_id: Optional[str] = None
    parent_id: Optional[str] = None
    status: Optional[TaskStatus] = None
    assignee_id: Optional[str] = None
    due: Optional[date] = None
    prio: Optional[Prio] = None
    notes: Optional[str] = None
    est: Optional[str] = None
    delegated: Optional[bool] = None
    sort_order: Optional[int] = None


class CommentCreate(BaseModel):
    person_id: str
    text: str


class TaskReorderRequest(BaseModel):
    ids: list[str]  # 같은 컬럼 내 새 순서대로 정렬된 task id 목록


# ---------- scaffold ----------
class ScaffoldItem(BaseModel):
    title: str
    est: str = ""


class ScaffoldOut(BaseModel):
    kind: str  # "연구 · 논문 작성" 등
    icon: str  # "brain"|"branch"|"file"|"list"
    items: list[ScaffoldItem]
    created: bool = False  # True면 실제 생성됨, False면 미리보기
    created_task_ids: list[str] = []


class ScaffoldRequest(BaseModel):
    create: bool = False  # True면 하위작업 실제 생성, False면 미리보기
    use_llm: bool = False  # LLM 보강 사용 여부


# ---------- risks ----------
class RiskOut(BaseModel):
    kind: str  # "지연 위험" | "업무 쏠림" | "의존성"
    icon: str  # "clock" | "scale" | "link"
    tone: str  # "coral" | "amber" | "violet"
    task_id: Optional[str] = None  # 관련 task id (cta 대상)
    text: str  # 한국어. 강조어를 ** ** 마크다운으로 감싼다
    cta: Optional[str] = None  # "작업 열기" | "후속 작업 보기"


# ---------- inbox ----------
class ClassificationOut(BaseModel):
    id: Optional[str] = None
    inbox_item_id: str
    type: ClsType
    sphere: Sphere
    project_id: Optional[str] = None
    proj_label: str
    tone: str
    due_text: str
    when_text: str
    extra: str
    reason: str
    confidence: float
    model: str
    created_at: Optional[datetime] = None


class InboxItemOut(BaseModel):
    id: str
    kind: InboxKind
    raw: str
    status: InboxStatus
    created_at: datetime
    materialized_task_id: Optional[str] = None
    classification: Optional[ClassificationOut] = None  # 최신 1건


class CaptureRequest(BaseModel):
    kind: InboxKind = InboxKind.text
    raw: str


class CaptureResponse(BaseModel):
    item: InboxItemOut
    classification: ClassificationOut


class ReclassifyRequest(BaseModel):
    type: Optional[ClsType] = None  # 사용자가 타입 강제 가능


class ConfirmResponse(BaseModel):
    item: InboxItemOut
    task: Optional[TaskNode] = None  # type=task 면 생성된 작업


# ---------- llm ----------
class LLMHealth(BaseModel):
    reachable: bool
    provider: str  # "ollama" | "heuristic"
    model: str
    host: Optional[str] = None
    detail: str = ""


# ---------- dashboard ----------
class UserOut(BaseModel):
    name: str  # 예: "지우"
    initial: str  # 예: "지"


class WeatherOut(BaseModel):
    temp: int  # 현재 기온 (예: 24)
    cond: str  # "맑음 · 한낮 28°"
    icon: str  # 'cloudSun' 저장 → 표시용 'sun' 으로 매핑


class BriefingOut(BaseModel):
    today: str  # 히어로 날짜 라벨 (예: "6월 7일 일요일")
    weather: WeatherOut
    commute: str
    sleep: str
    note: str  # HTML 허용


class EventOut(BaseModel):
    id: str
    time: str
    title: str
    tag: str
    dur: str
    tone: str
    soon: bool


class ApprovalSummaryOut(BaseModel):
    id: str
    icon: str
    tone: str
    title: str
    time: str
    # risk=="high" 만 포함, 최대 3건. detail/cta/alt/undo_label 미포함.


class GoalOut(BaseModel):
    id: str
    title: str
    pct: int
    sub: str
    tone: str  # tone=키('blue' 등)


class InboxRecentOut(BaseModel):
    id: str
    kind: str
    raw: str
    type: str
    proj_label: str
    tone: str
    # 평탄화 형태: 최신 classification 에서 type/proj_label/tone 을 끌어와 채운다.


class TaskSummaryItem(BaseModel):
    id: str
    title: str
    prio: str
    project: str


class TaskSummaryOut(BaseModel):
    open_count: int  # 미완료 작업 수
    items: list[TaskSummaryItem]


class BadgesOut(BaseModel):
    appr: int  # 결재함 대기(=high risk 건수)
    task: int  # 미완료 작업 수(상단 작업 배지)
    noti: int  # 알림 배지 (시드 상수 6)


class DashboardOut(BaseModel):
    user: UserOut
    briefing: BriefingOut
    saved_today: str  # "47분"
    today_routed: int  # 7
    schedule: list[EventOut]
    task_summary: TaskSummaryOut
    goals: list[GoalOut]
    approvals_summary: list[ApprovalSummaryOut]  # high-risk 만, 최대 3건
    inbox_recent: list[InboxRecentOut]  # 최근 3 (평탄화 형태)
    badges: BadgesOut


# 재귀 모델 전방참조 해소
ProjectNode.model_rebuild()
TaskNode.model_rebuild()


# ==================== phase-7: 결재함 + 자동화 ====================


# ---------- 결재함 ----------
class ApprovalOut(BaseModel):
    id: str
    icon: str
    tone: str
    risk: str  # "low" | "high"
    time: str
    title: str
    detail: str
    cta: str
    alt: str
    undo_label: str
    status: str  # pending|approved|executed|undone
    source: str
    rule_id: Optional[str] = None
    sort_order: int


class ApprovalLogOut(BaseModel):
    id: str
    time: str
    text: str
    approval_id: Optional[str] = None


class AutonomyOut(BaseModel):
    level: str  # approval_first|mixed|full_auto


class AutonomyPatch(BaseModel):
    level: str  # 셋 중 하나만 허용(검증)


class ApprovalQueueOut(BaseModel):
    """결재함 페이지 응답 정본. 자율성 레벨로 큐를 분기한 결과."""

    autonomy: str
    saved_today: str  # "47분"
    auto_count_night: int  # 7
    pending: list[ApprovalOut]
    done: list[ApprovalOut]
    log: list[ApprovalLogOut]
    badges_appr: int  # 상단 결재함 배지 = pending 수


# ---------- 자동화 ----------
class FlowOut(BaseModel):
    trigger: str
    cond: Optional[str] = None
    action: str


class RuleOut(BaseModel):
    id: str
    name: str
    cat: str  # mail|cal|focus|life
    trigger: str
    cond: Optional[str] = None
    action: str
    on: bool
    last: str
    runs: int
    fresh: bool
    source: str


class RuleCreate(BaseModel):
    name: str
    cat: str
    trigger: str
    cond: Optional[str] = None
    action: str
    source: Optional[str] = "user"


class RulePatch(BaseModel):
    name: Optional[str] = None
    cat: Optional[str] = None
    trigger: Optional[str] = None
    cond: Optional[str] = None
    action: Optional[str] = None
    on: Optional[bool] = None


class ParseRequest(BaseModel):
    text: str


class ParsePreviewOut(BaseModel):
    matched: bool
    name: str
    cat: str  # mail|cal|focus|life
    parse: FlowOut
    model: str  # "llm:<model>" | "heuristic" | "example"
    confidence: float


class SuggestionOut(BaseModel):
    id: str
    pattern: str
    offer: FlowOut
    offer_name: str
    offer_cat: str
    status: str


class RunLogOut(BaseModel):
    id: str
    time: str
    rule: str
    text: str
    undone: bool


class AutomationStatsOut(BaseModel):
    active: int
    runs_week: int
    saved: str


class AutomationPageOut(BaseModel):
    """자동화 페이지 응답 정본(한 번에 모든 뷰 데이터)."""

    stats: AutomationStatsOut
    rules: list[RuleOut]
    suggests: list[SuggestionOut]
    log: list[RunLogOut]
    examples: list[ParsePreviewOut]


# ==================== phase-8: 일정 + 회의 도우미 ====================


class CalendarOut(BaseModel):
    id: str
    name: str
    tone: str
    on: bool
    count: int = 0


class EventActionOut(BaseModel):
    text: str
    when: str


class AttendeeOut(BaseModel):
    email: str
    name: str = ""
    status: str = ""  # accepted|declined|tentative|needsAction


class CalEventOut(BaseModel):
    id: str
    day: int
    date: str = ""  # 실제 날짜 ISO "YYYY-MM-DD"
    start: str
    end: str
    title: str
    cal: str
    loc: str = ""
    note: str = ""
    soon: bool = False
    people: list[str] = []
    rrule: str = ""  # 반복 규칙(iCal RRULE)
    reminders: list[int] = []  # 시작 N분 전 알림
    attendees: list[AttendeeOut] = []
    response_status: str = ""  # 내 RSVP
    actions: list[EventActionOut] = []
    has_meeting: bool = False
    meet_label: str = ""


class EventWriteRequest(BaseModel):
    """새 일정 생성/수정 요청. start 빈값 = 종일."""

    title: str
    date: str  # ISO "YYYY-MM-DD"
    start: str = ""  # "HH:MM"
    end: str = ""  # "HH:MM"
    loc: str = ""
    note: str = ""
    people: list[str] = []  # 참석자 이메일
    cal: str = ""  # 대상 캘린더 id(빈값=기본 연결 캘린더 또는 로컬)
    rrule: str = ""  # 반복 규칙(빈값=단일). 예 "FREQ=WEEKLY;BYDAY=MO"
    reminders: list[int] = []  # 시작 N분 전 알림(분 단위)


class EventRsvpRequest(BaseModel):
    status: str  # accepted | declined | tentative


class FocusBlockOut(BaseModel):
    id: str
    day: int
    date: str = ""  # 실제 날짜 ISO(데모는 6월로 매핑)
    start: str
    end: str
    title: str
    type: str
    tag: str
    task_id: Optional[str] = None
    auto: bool = True
    sort_order: int = 0


class MeetingActionOut(BaseModel):
    id: str
    idx: int
    text: str
    who: str
    when: str = ""
    added: bool
    materialized_task_id: Optional[str] = None


class MeetingOut(BaseModel):
    id: str
    event_id: str
    phase: str
    one_on_one: bool = False
    meta: str = ""
    starts_in: str = ""
    summary: list[str] = []
    decisions: list[str] = []
    agenda: list[str] = []
    last_meeting: dict[str, Any] = {}
    last_actions: list[dict[str, Any]] = []
    insights: list[str] = []
    docs: list[str] = []
    person: dict[str, Any] = {}
    promises: list[dict[str, Any]] = []
    signals: list[dict[str, Any]] = []
    talking_points: list[str] = []
    actions: list[MeetingActionOut] = []


class DayBundleOut(BaseModel):
    day: int
    date: str = ""
    events: list[CalEventOut]
    focus_blocks: list[FocusBlockOut]


class WeekBundleOut(BaseModel):
    today: int
    today_date: str = ""  # 실제 오늘 ISO(real 연결 시 실날짜, 아니면 데모 2026-06-08)
    mode: str = "demo"  # "real" | "demo"
    week: list[int]
    weekdays: list[str]
    calendars: list[CalendarOut]
    events: list[CalEventOut]
    focus_blocks: list[FocusBlockOut]


class MaterializeResponse(BaseModel):
    action: MeetingActionOut
    task: Optional[TaskNode] = None
    all_added: bool = False


class FocusSuggestRequest(BaseModel):
    day: int
    min_minutes: int = 20
    create: bool = False


class FocusSuggestResponse(BaseModel):
    day: int
    suggestions: list[FocusBlockOut]
    created: bool = False


MaterializeResponse.model_rebuild()


# ==================== phase-9: 메일 + 알림 ====================


class MailAccountOut(BaseModel):
    id: str
    name: str
    email: str
    tone: str
    kind: str
    unread: int  # 집중(focused) 받은편지함 안읽음
    others: int = 0  # 기타(Outlook other) 받은편지함 안읽음


class MailFolderOut(BaseModel):
    id: str
    name: str
    icon: str


class AccountFolderOut(BaseModel):
    """계정별 실제 폴더/라벨(사이드바 트리). slug 가 list_mail folder 필터 키."""

    account: str
    slug: str
    name: str
    kind: str  # system | category | label | custom
    icon: str
    unread: int = 0
    total: int = 0
    sort_order: int = 0


class AiTaskOut(BaseModel):
    text: str
    project: str
    due: str = ""
    prio: str = ""


class AiEventOut(BaseModel):
    title: str
    date: str = ""
    day: str = ""
    time: str = ""
    dur: str = ""
    place: str = ""


class AiReplyOut(BaseModel):
    tone: str
    preview: str
    body: str


class AiFileOut(BaseModel):
    project: str
    reason: str


class AiAnalysisOut(BaseModel):
    summary: str = ""
    priority: str = "보통"
    category: str = ""
    tasks: list[AiTaskOut] = []
    events: list[AiEventOut] = []
    replies: list[AiReplyOut] = []
    file: Optional[AiFileOut] = None


class MailAttachmentOut(BaseModel):
    id: str = ""  # 프로바이더 attachment id(다운로드용)
    name: str
    mime: str = ""
    size: int = 0


class EmailRowOut(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    id: str
    account: str
    from_: str = Field(alias="from")
    from_name: str = ""  # 표시 이름(이름 없으면 도메인 추정) — 목록/리더에서 노출
    from_email: str = ""  # 실제 메일 주소(리더 보조 표시)
    to: str
    cc: str = ""
    thread_id: str = ""
    subject: str
    time: str
    read: bool
    starred: bool
    has_attach: bool
    labels: list[str] = []
    preview: str
    category: str = "primary"  # 카테고리 탭(primary/promotions/social/updates) — 라벨에서 산출
    ai: Optional[AiAnalysisOut] = None


class EmailDetailOut(EmailRowOut):
    date: str
    body: list[str] = []
    body_html: str = ""  # 원본 HTML 본문(샌드박스 iframe 렌더용; cid 는 첨부 URL 로 치환됨)
    attachments: list[MailAttachmentOut] = []


class ConversationOut(BaseModel):
    """대화(스레드) 묶음 — 같은 thread_id 메일들."""

    thread_id: str
    subject: str
    count: int
    messages: list[EmailRowOut] = []


class ExtractRequest(BaseModel):
    kind: str  # task | event | file
    index: int = 0


class ExtractResponse(BaseModel):
    kind: str
    task: Optional[TaskNode] = None
    event_id: Optional[str] = None
    project: Optional[str] = None
    message: str


class ExtractAllResponse(BaseModel):
    created_task_ids: list[str] = []
    created_event_ids: list[str] = []
    filed_project: Optional[str] = None
    message: str


class ReplyDraftRequest(BaseModel):
    reply_index: Optional[int] = None
    tone: Optional[str] = None


class ReplyDraftOut(BaseModel):
    to: str
    from_account: str
    subject: str
    body: str


class ComposeAttachment(BaseModel):
    name: str
    mime: str = "application/octet-stream"
    data_b64: str  # base64 인코딩 본문


class SendRequest(BaseModel):
    from_account: str
    to: str
    cc: str = ""
    bcc: str = ""
    subject: str
    body: str
    in_reply_to: Optional[str] = None
    attachments: list[ComposeAttachment] = []


class SendResponse(BaseModel):
    approval_id: str
    status: str
    message: str


class MailStarRequest(BaseModel):
    starred: bool


class MailReadRequest(BaseModel):
    read: bool


class MailLabelRequest(BaseModel):
    """라벨/카테고리 추가·제거(폴더 이동 포함)."""

    add: list[str] = []
    remove: list[str] = []


class BulkMailRequest(BaseModel):
    ids: list[str]
    action: str  # read | unread | archive | unarchive | trash | star | unstar


# ---------- 알림 ----------
class NotificationOut(BaseModel):
    id: str
    bucket: str
    src: str
    app: str
    title: str
    sum: str
    time: str
    why: str
    tone: str
    act: str = ""


class TriageOut(BaseModel):
    now: list[NotificationOut]
    later: list[NotificationOut]
    held: list[NotificationOut]
    counts: dict
    stats: dict


class ReclassifyNotifRequest(BaseModel):
    to_bucket: str


class GuardOut(BaseModel):
    on: bool
    until: str
    held: int
    quiet: str


class GuardPatch(BaseModel):
    on: bool


class DigestOut(BaseModel):
    id: str
    time: str
    name: str
    desc: str
    count: int
    done: bool


class SenderRuleOut(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    id: str
    who: str
    from_label: str = Field(serialization_alias="from")
    rule: str
    tone: str
    bucket: str


class SenderRuleCreate(BaseModel):
    who: str
    from_label: str
    rule: str
    bucket: str = "now"
    tone: str = "ink"


ExtractResponse.model_rebuild()


# ==================== phase-12: 여정 + 하루 마감 ====================


class JourneyPersonOut(BaseModel):
    id: str
    name: str
    initial: str
    color: str
    me: bool = False
    tasks: int = 0
    role: str = ""


class JourneyStageOut(BaseModel):
    id: str
    title: str
    time: str
    tone: str
    n: str


class RoutineCardOut(BaseModel):
    node: str
    icon: str
    title: str
    meta: str
    tag: str
    tool: str
    toolTone: str = ""


class TaskCardOut(BaseModel):
    node: str
    title: str
    meta: str
    who: str
    done: bool = False


class MeetCardOut(BaseModel):
    node: str
    time: str
    title: str
    meta: str
    who: str
    soon: bool = False


class TileCardOut(BaseModel):
    node: str
    hi: bool = False
    icon: str
    tt: str
    ts: str


class JourneyCardsOut(BaseModel):
    s1: list[RoutineCardOut]
    s2: list[TaskCardOut]
    s3: list[MeetCardOut]
    s4: list[TileCardOut]


class JourneyLinkOut(BaseModel):
    from_node: str
    to_node: str
    tone: str
    dash: bool = False


class JourneyRowOut(BaseModel):
    node: str
    title: str
    star: bool = False
    statusKey: str
    statusLabel: str
    startTime: str
    who: str


class GaugeOut(BaseModel):
    pct: int
    tone: str
    val: str
    unit: str
    label: str
    sub: str


class JourneyMetaOut(BaseModel):
    stageCount: int
    itemCount: int
    collabCount: int
    peopleMore: str = "+3"


class JourneyHeaderOut(BaseModel):
    dateLabel: str
    weather: str


class JourneyTableOut(BaseModel):
    subtitle: str


class JourneyOut(BaseModel):
    header: JourneyHeaderOut
    meta: JourneyMetaOut
    people: list[JourneyPersonOut]
    stages: list[JourneyStageOut]
    cards: JourneyCardsOut
    links: list[JourneyLinkOut]
    rows: list[JourneyRowOut]
    table: JourneyTableOut
    gauges: list[GaugeOut]
    gaugeFoot: str


# ==================== phase-13: 외부 연동 & 데이터 커넥터 ====================
class ConnectorStatusOut(BaseModel):
    id: str
    domain: str
    provider: str
    name: str  # "회사" / "우리카드" / "Apple Health"
    kind: str  # "Lumi Workspace" / "신용/체크" / "건강 앱"
    tone: str  # blue|violet|coral|green|amber|ink|faint
    mode: str  # mock | csv | healthkit | real
    env_mode: str  # CONNECTOR_<DOMAIN> 값(real 허용 여부)
    state: str  # connected|disconnected|syncing|error|token_expired
    on: bool  # state==connected (라이프 카드 on 호환)
    last: str  # last_label
    last_synced_at: Optional[datetime] = None
    error_detail: str = ""
    email: str = ""  # phase-16 연결된 실계정 주소(external_account_id)

    @classmethod
    def from_account(cls, a, env_mode: str) -> ConnectorStatusOut:
        dom = a.domain.value if hasattr(a.domain, "value") else a.domain
        mode = a.mode.value if hasattr(a.mode, "value") else a.mode
        state = a.state.value if hasattr(a.state, "value") else a.state
        return cls(
            id=a.id,
            domain=dom,
            provider=a.provider,
            name=a.name,
            kind=a.kind,
            tone=a.tone,
            mode=mode,
            env_mode=env_mode,
            state=state,
            on=(state == "connected"),
            last=a.last_label,
            last_synced_at=a.last_synced_at,
            error_detail=a.error_detail,
            email=getattr(a, "external_account_id", "") or "",
        )


class OAuthStartOut(BaseModel):
    authorize_url: str


class ConnectorProviderOut(BaseModel):
    """phase-16 '계정 추가' 버튼이 보여줄 OAuth provider 목록 + 구성 여부."""

    domain: str  # mail | calendar | knowledge ...
    provider: str  # gmail | outlook | google_calendar | notion
    label: str  # "Gmail" / "Outlook"
    configured: bool  # client_id 설정되어 real 연동 가능


class SyncResultOut(BaseModel):
    domain: str
    account_id: str
    seen: int = 0
    upserted: int = 0
    skipped: int = 0
    errors: int = 0
    events_published: list[str] = Field(default_factory=list)
    detail: str = ""


class ImportResultOut(BaseModel):
    entity_type: str
    imported: int = 0
    skipped: int = 0
    errors: int = 0
    detail: str = ""


# ==================== phase-14: 능동 워커 + 멀티모달 ====================
class ProactiveCardOut(BaseModel):
    id: str
    kind: str
    icon: str
    tone: str
    title: str
    why: str
    cta: Optional[str] = None
    action_kind: Optional[str] = None
    detected_at: str
    status: str


class ProactiveAcceptOut(BaseModel):
    id: str
    action_kind: str
    created_id: Optional[str] = None
    message: str


class WeeklyReviewOut(BaseModel):
    id: str
    week_label: str
    deep_work_h: float
    meeting_h: float
    done_count: int
    auto_count: int
    saved_minutes: int
    patterns: list[dict] = Field(default_factory=list)
    note: Optional[str] = None


class WorkerRunOut(BaseModel):
    job: str
    ok: bool
    detail: str


class TranscribeOut(BaseModel):
    text: str
    seconds: float
    model: str
    confidence: float


class CaptionOut(BaseModel):
    text: str
    ocr: str
    model: str
    confidence: float


# ==================== phase-15: 인증/멀티유저 + 운영 ====================
class LoginIn(BaseModel):
    email: str
    password: str


class MeOut(BaseModel):
    id: str
    name: str
    initial: str
    email: str
    role: str  # member | admin


class TokenOut(BaseModel):
    id: str
    name: str
    token: Optional[str] = None  # 발급 시 1회만 평문
    created_at: str


class ReadyOut(BaseModel):
    ready: bool
    checks: dict[str, str]


# ==================== 설정(settings) ====================
class ProfileUpdateIn(BaseModel):
    """프로필 편집. 모두 선택 — 주어진 필드만 갱신."""

    name: Optional[str] = None
    role: Optional[str] = None


class TokenInfoOut(BaseModel):
    """토큰 목록 항목(평문 미포함)."""

    id: str
    name: str
    created_at: str
    last_used_at: Optional[str] = None
    revoked: bool = False


class LLMConfigOut(BaseModel):
    """유효 LLM 연결 설정 + 선택지 + 보조 프로바이더(읽기전용)."""

    provider: str  # 유효(오버레이>env): auto | ollama | heuristic
    model: str
    host: str
    timeout: float
    provider_options: list[str] = ["ollama"]
    overridden: bool = False  # 런타임 오버레이가 활성인가(env 기본과 다른가)
    # 보조 프로바이더(읽기전용, env 관리)
    stt_provider: str = ""
    vision_provider: str = ""


class LLMUpdateIn(BaseModel):
    """LLM 연결 저장. None/빈 문자열은 '기본으로 되돌리기'."""

    provider: Optional[str] = None
    model: Optional[str] = None
    host: Optional[str] = None
    timeout: Optional[float] = None


class LLMTestIn(BaseModel):
    """연결 테스트(저장 없이). 미지정 필드는 유효 설정값 사용."""

    provider: Optional[str] = None
    host: Optional[str] = None
    model: Optional[str] = None


class LLMTestOut(BaseModel):
    reachable: bool
    provider: str
    model: str
    host: Optional[str] = None
    models: list[str] = []  # ollama /api/tags 결과
    detail: str = ""


class SystemConfigOut(BaseModel):
    """런타임/환경 상태 스냅샷(읽기전용). env(.env)로 관리됨을 명시."""

    env: str  # dev | prod
    auth_enabled: bool
    worker_enabled: bool
    database: str  # sqlite | postgres | ...
    sync_interval_minutes: int
    sync_page_size: int
    connector_modes: dict[str, str]  # {calendar: mock, mail: mock}

